Merge tag 'char-misc-4.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc updates from Greg KH: "Here is the big char/misc driver update for 4.6-rc1. The majority of the patches here is hwtracing and some new mic drivers, but there's a lot of other driver updates as well. Full details in the shortlog. All have been in linux-next for a while with no reported issues" * tag 'char-misc-4.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (238 commits) goldfish: Fix build error of missing ioremap on UM nvmem: mediatek: Fix later provider initialization nvmem: imx-ocotp: Fix return value of imx_ocotp_read nvmem: Fix dependencies for !HAS_IOMEM archs char: genrtc: replace blacklist with whitelist drivers/hwtracing: make coresight-etm-perf.c explicitly non-modular drivers: char: mem: fix IS_ERROR_VALUE usage char: xillybus: Fix internal data structure initialization pch_phub: return -ENODATA if ROM can't be mapped Drivers: hv: vmbus: Support kexec on ws2012 r2 and above Drivers: hv: vmbus: Support handling messages on multiple CPUs Drivers: hv: utils: Remove util transport handler from list if registration fails Drivers: hv: util: Pass the channel information during the init call Drivers: hv: vmbus: avoid unneeded compiler optimizations in vmbus_wait_for_unload() Drivers: hv: vmbus: remove code duplication in message handling Drivers: hv: vmbus: avoid wait_for_completion() on crash Drivers: hv: vmbus: don't loose HVMSG_TIMER_EXPIRED messages misc: at24: replace memory_accessor with nvmem_device_read eeprom: 93xx46: extend driver to plug into the NVMEM framework eeprom: at25: extend driver to plug into the NVMEM framework ...
This commit is contained in:
@@ -440,7 +440,7 @@ config ARM_CHARLCD
|
||||
still useful.
|
||||
|
||||
config BMP085
|
||||
bool
|
||||
tristate
|
||||
depends on SYSFS
|
||||
|
||||
config BMP085_I2C
|
||||
@@ -470,7 +470,7 @@ config BMP085_SPI
|
||||
config PCH_PHUB
|
||||
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"
|
||||
select GENERIC_NET_UTILS
|
||||
depends on PCI && (X86_32 || COMPILE_TEST)
|
||||
depends on PCI && (X86_32 || MIPS || COMPILE_TEST)
|
||||
help
|
||||
This driver is for PCH(Platform controller Hub) PHUB(Packet Hub) of
|
||||
Intel Topcliff which is an IOH(Input/Output Hub) for x86 embedded
|
||||
|
@@ -216,7 +216,7 @@ static s32 dpot_read_i2c(struct dpot_data *dpot, u8 reg)
|
||||
*/
|
||||
value = swab16(value);
|
||||
|
||||
if (dpot->uid == DPOT_UID(AD5271_ID))
|
||||
if (dpot->uid == DPOT_UID(AD5274_ID))
|
||||
value = value >> 2;
|
||||
return value;
|
||||
default:
|
||||
@@ -452,7 +452,7 @@ static ssize_t sysfs_set_reg(struct device *dev,
|
||||
int err;
|
||||
|
||||
if (reg & DPOT_ADDR_OTP_EN) {
|
||||
if (!strncmp(buf, "enabled", sizeof("enabled")))
|
||||
if (sysfs_streq(buf, "enabled"))
|
||||
set_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask);
|
||||
else
|
||||
clear_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask);
|
||||
|
@@ -1215,7 +1215,7 @@ static int apds990x_remove(struct i2c_client *client)
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int apds990x_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
apds990x_chip_off(chip);
|
||||
@@ -1224,7 +1224,7 @@ static int apds990x_suspend(struct device *dev)
|
||||
|
||||
static int apds990x_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
/*
|
||||
@@ -1240,7 +1240,7 @@ static int apds990x_resume(struct device *dev)
|
||||
#ifdef CONFIG_PM
|
||||
static int apds990x_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
apds990x_chip_off(chip);
|
||||
@@ -1249,7 +1249,7 @@ static int apds990x_runtime_suspend(struct device *dev)
|
||||
|
||||
static int apds990x_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
apds990x_chip_on(chip);
|
||||
|
@@ -8,7 +8,6 @@
|
||||
* Author: Linus Walleij <triad@df.lth.se>
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
@@ -328,20 +327,6 @@ out_no_resource:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit charlcd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct charlcd *lcd = platform_get_drvdata(pdev);
|
||||
|
||||
if (lcd) {
|
||||
free_irq(lcd->irq, lcd);
|
||||
iounmap(lcd->virtbase);
|
||||
release_mem_region(lcd->phybase, lcd->physize);
|
||||
kfree(lcd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int charlcd_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
@@ -376,13 +361,8 @@ static struct platform_driver charlcd_driver = {
|
||||
.driver = {
|
||||
.name = DRIVERNAME,
|
||||
.pm = &charlcd_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
.of_match_table = of_match_ptr(charlcd_match),
|
||||
},
|
||||
.remove = __exit_p(charlcd_remove),
|
||||
};
|
||||
|
||||
module_platform_driver_probe(charlcd_driver, charlcd_probe);
|
||||
|
||||
MODULE_AUTHOR("Linus Walleij <triad@df.lth.se>");
|
||||
MODULE_DESCRIPTION("ARM Character LCD Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
builtin_platform_driver_probe(charlcd_driver, charlcd_probe);
|
||||
|
@@ -1323,7 +1323,7 @@ static int bh1770_remove(struct i2c_client *client)
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int bh1770_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
bh1770_chip_off(chip);
|
||||
@@ -1333,7 +1333,7 @@ static int bh1770_suspend(struct device *dev)
|
||||
|
||||
static int bh1770_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
int ret = 0;
|
||||
|
||||
@@ -1361,7 +1361,7 @@ static int bh1770_resume(struct device *dev)
|
||||
#ifdef CONFIG_PM
|
||||
static int bh1770_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
bh1770_chip_off(chip);
|
||||
@@ -1371,7 +1371,7 @@ static int bh1770_runtime_suspend(struct device *dev)
|
||||
|
||||
static int bh1770_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
bh1770_chip_on(chip);
|
||||
|
@@ -721,9 +721,7 @@ static ssize_t c2port_read_flash_data(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buffer, loff_t offset, size_t count)
|
||||
{
|
||||
struct c2port_device *c2dev =
|
||||
dev_get_drvdata(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj));
|
||||
ssize_t ret;
|
||||
|
||||
/* Check the device and flash access status */
|
||||
@@ -838,9 +836,7 @@ static ssize_t c2port_write_flash_data(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buffer, loff_t offset, size_t count)
|
||||
{
|
||||
struct c2port_device *c2dev =
|
||||
dev_get_drvdata(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj));
|
||||
int ret;
|
||||
|
||||
/* Check the device access status */
|
||||
|
@@ -386,8 +386,7 @@ static ssize_t afu_eb_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct cxl_afu *afu = to_cxl_afu(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct cxl_afu *afu = to_cxl_afu(kobj_to_dev(kobj));
|
||||
|
||||
return cxl_afu_read_err_buffer(afu, buf, off, count);
|
||||
}
|
||||
@@ -467,7 +466,7 @@ static ssize_t afu_read_config(struct file *filp, struct kobject *kobj,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct afu_config_record *cr = to_cr(kobj);
|
||||
struct cxl_afu *afu = to_cxl_afu(container_of(kobj->parent, struct device, kobj));
|
||||
struct cxl_afu *afu = to_cxl_afu(kobj_to_dev(kobj->parent));
|
||||
|
||||
u64 i, j, val;
|
||||
|
||||
|
@@ -3,6 +3,8 @@ menu "EEPROM support"
|
||||
config EEPROM_AT24
|
||||
tristate "I2C EEPROMs / RAMs / ROMs from most vendors"
|
||||
depends on I2C && SYSFS
|
||||
select REGMAP
|
||||
select NVMEM
|
||||
help
|
||||
Enable this driver to get read/write support to most I2C EEPROMs
|
||||
and compatible devices like FRAMs, SRAMs, ROMs etc. After you
|
||||
@@ -30,6 +32,8 @@ config EEPROM_AT24
|
||||
config EEPROM_AT25
|
||||
tristate "SPI EEPROMs from most vendors"
|
||||
depends on SPI && SYSFS
|
||||
select REGMAP
|
||||
select NVMEM
|
||||
help
|
||||
Enable this driver to get read/write support to most SPI EEPROMs,
|
||||
after you configure the board init code to know about each eeprom
|
||||
@@ -74,6 +78,8 @@ config EEPROM_93CX6
|
||||
config EEPROM_93XX46
|
||||
tristate "Microwire EEPROM 93XX46 support"
|
||||
depends on SPI && SYSFS
|
||||
select REGMAP
|
||||
select NVMEM
|
||||
help
|
||||
Driver for the microwire EEPROM chipsets 93xx46x. The driver
|
||||
supports both read and write commands and also the command to
|
||||
|
@@ -15,7 +15,6 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/bitops.h>
|
||||
@@ -23,6 +22,8 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/platform_data/at24.h>
|
||||
|
||||
/*
|
||||
@@ -55,7 +56,6 @@
|
||||
|
||||
struct at24_data {
|
||||
struct at24_platform_data chip;
|
||||
struct memory_accessor macc;
|
||||
int use_smbus;
|
||||
int use_smbus_write;
|
||||
|
||||
@@ -64,12 +64,15 @@ struct at24_data {
|
||||
* but not from changes by other I2C masters.
|
||||
*/
|
||||
struct mutex lock;
|
||||
struct bin_attribute bin;
|
||||
|
||||
u8 *writebuf;
|
||||
unsigned write_max;
|
||||
unsigned num_addresses;
|
||||
|
||||
struct regmap_config regmap_config;
|
||||
struct nvmem_config nvmem_config;
|
||||
struct nvmem_device *nvmem;
|
||||
|
||||
/*
|
||||
* Some chips tie up multiple I2C addresses; dummy devices reserve
|
||||
* them for us, and we'll use them with SMBus calls.
|
||||
@@ -283,17 +286,6 @@ static ssize_t at24_read(struct at24_data *at24,
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct at24_data *at24;
|
||||
|
||||
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
return at24_read(at24, buf, off, count);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Note that if the hardware write-protect pin is pulled high, the whole
|
||||
* chip is normally write protected. But there are plenty of product
|
||||
@@ -414,40 +406,49 @@ static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct at24_data *at24;
|
||||
|
||||
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
return at24_write(at24, buf, off, count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* This lets other kernel code access the eeprom data. For example, it
|
||||
* might hold a board's Ethernet address, or board-specific calibration
|
||||
* data generated on the manufacturing floor.
|
||||
*/
|
||||
|
||||
static ssize_t at24_macc_read(struct memory_accessor *macc, char *buf,
|
||||
off_t offset, size_t count)
|
||||
* Provide a regmap interface, which is registered with the NVMEM
|
||||
* framework
|
||||
*/
|
||||
static int at24_regmap_read(void *context, const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
struct at24_data *at24 = container_of(macc, struct at24_data, macc);
|
||||
struct at24_data *at24 = context;
|
||||
off_t offset = *(u32 *)reg;
|
||||
int err;
|
||||
|
||||
return at24_read(at24, buf, offset, count);
|
||||
err = at24_read(at24, val, offset, val_size);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t at24_macc_write(struct memory_accessor *macc, const char *buf,
|
||||
off_t offset, size_t count)
|
||||
static int at24_regmap_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct at24_data *at24 = container_of(macc, struct at24_data, macc);
|
||||
struct at24_data *at24 = context;
|
||||
const char *buf;
|
||||
u32 offset;
|
||||
size_t len;
|
||||
int err;
|
||||
|
||||
return at24_write(at24, buf, offset, count);
|
||||
memcpy(&offset, data, sizeof(offset));
|
||||
buf = (const char *)data + sizeof(offset);
|
||||
len = count - sizeof(offset);
|
||||
|
||||
err = at24_write(at24, buf, offset, len);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_bus at24_regmap_bus = {
|
||||
.read = at24_regmap_read,
|
||||
.write = at24_regmap_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
@@ -481,6 +482,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
struct at24_data *at24;
|
||||
int err;
|
||||
unsigned i, num_addresses;
|
||||
struct regmap *regmap;
|
||||
|
||||
if (client->dev.platform_data) {
|
||||
chip = *(struct at24_platform_data *)client->dev.platform_data;
|
||||
@@ -573,29 +575,12 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
at24->chip = chip;
|
||||
at24->num_addresses = num_addresses;
|
||||
|
||||
/*
|
||||
* Export the EEPROM bytes through sysfs, since that's convenient.
|
||||
* By default, only root should see the data (maybe passwords etc)
|
||||
*/
|
||||
sysfs_bin_attr_init(&at24->bin);
|
||||
at24->bin.attr.name = "eeprom";
|
||||
at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
|
||||
at24->bin.read = at24_bin_read;
|
||||
at24->bin.size = chip.byte_len;
|
||||
|
||||
at24->macc.read = at24_macc_read;
|
||||
|
||||
writable = !(chip.flags & AT24_FLAG_READONLY);
|
||||
if (writable) {
|
||||
if (!use_smbus || use_smbus_write) {
|
||||
|
||||
unsigned write_max = chip.page_size;
|
||||
|
||||
at24->macc.write = at24_macc_write;
|
||||
|
||||
at24->bin.write = at24_bin_write;
|
||||
at24->bin.attr.mode |= S_IWUSR;
|
||||
|
||||
if (write_max > io_limit)
|
||||
write_max = io_limit;
|
||||
if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
|
||||
@@ -627,14 +612,38 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
}
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
|
||||
if (err)
|
||||
at24->regmap_config.reg_bits = 32;
|
||||
at24->regmap_config.val_bits = 8;
|
||||
at24->regmap_config.reg_stride = 1;
|
||||
at24->regmap_config.max_register = chip.byte_len - 1;
|
||||
|
||||
regmap = devm_regmap_init(&client->dev, &at24_regmap_bus, at24,
|
||||
&at24->regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "regmap init failed\n");
|
||||
err = PTR_ERR(regmap);
|
||||
goto err_clients;
|
||||
}
|
||||
|
||||
at24->nvmem_config.name = dev_name(&client->dev);
|
||||
at24->nvmem_config.dev = &client->dev;
|
||||
at24->nvmem_config.read_only = !writable;
|
||||
at24->nvmem_config.root_only = true;
|
||||
at24->nvmem_config.owner = THIS_MODULE;
|
||||
at24->nvmem_config.compat = true;
|
||||
at24->nvmem_config.base_dev = &client->dev;
|
||||
|
||||
at24->nvmem = nvmem_register(&at24->nvmem_config);
|
||||
|
||||
if (IS_ERR(at24->nvmem)) {
|
||||
err = PTR_ERR(at24->nvmem);
|
||||
goto err_clients;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, at24);
|
||||
|
||||
dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n",
|
||||
at24->bin.size, client->name,
|
||||
dev_info(&client->dev, "%u byte %s EEPROM, %s, %u bytes/write\n",
|
||||
chip.byte_len, client->name,
|
||||
writable ? "writable" : "read-only", at24->write_max);
|
||||
if (use_smbus == I2C_SMBUS_WORD_DATA ||
|
||||
use_smbus == I2C_SMBUS_BYTE_DATA) {
|
||||
@@ -645,7 +654,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
|
||||
/* export data to kernel code */
|
||||
if (chip.setup)
|
||||
chip.setup(&at24->macc, chip.context);
|
||||
chip.setup(at24->nvmem, chip.context);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -663,7 +672,8 @@ static int at24_remove(struct i2c_client *client)
|
||||
int i;
|
||||
|
||||
at24 = i2c_get_clientdata(client);
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);
|
||||
|
||||
nvmem_unregister(at24->nvmem);
|
||||
|
||||
for (i = 1; i < at24->num_addresses; i++)
|
||||
i2c_unregister_device(at24->client[i]);
|
||||
|
@@ -16,6 +16,8 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/eeprom.h>
|
||||
#include <linux/property.h>
|
||||
@@ -29,11 +31,12 @@
|
||||
|
||||
struct at25_data {
|
||||
struct spi_device *spi;
|
||||
struct memory_accessor mem;
|
||||
struct mutex lock;
|
||||
struct spi_eeprom chip;
|
||||
struct bin_attribute bin;
|
||||
unsigned addrlen;
|
||||
struct regmap_config regmap_config;
|
||||
struct nvmem_config nvmem_config;
|
||||
struct nvmem_device *nvmem;
|
||||
};
|
||||
|
||||
#define AT25_WREN 0x06 /* latch the write enable */
|
||||
@@ -77,10 +80,10 @@ at25_ee_read(
|
||||
struct spi_message m;
|
||||
u8 instr;
|
||||
|
||||
if (unlikely(offset >= at25->bin.size))
|
||||
if (unlikely(offset >= at25->chip.byte_len))
|
||||
return 0;
|
||||
if ((offset + count) > at25->bin.size)
|
||||
count = at25->bin.size - offset;
|
||||
if ((offset + count) > at25->chip.byte_len)
|
||||
count = at25->chip.byte_len - offset;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
@@ -131,21 +134,19 @@ at25_ee_read(
|
||||
return status ? status : count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
static int at25_regmap_read(void *context, const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
struct at25_data *at25 = context;
|
||||
off_t offset = *(u32 *)reg;
|
||||
int err;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
|
||||
return at25_ee_read(at25, buf, off, count);
|
||||
err = at25_ee_read(at25, val, offset, val_size);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,
|
||||
size_t count)
|
||||
@@ -155,10 +156,10 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,
|
||||
unsigned buf_size;
|
||||
u8 *bounce;
|
||||
|
||||
if (unlikely(off >= at25->bin.size))
|
||||
if (unlikely(off >= at25->chip.byte_len))
|
||||
return -EFBIG;
|
||||
if ((off + count) > at25->bin.size)
|
||||
count = at25->bin.size - off;
|
||||
if ((off + count) > at25->chip.byte_len)
|
||||
count = at25->chip.byte_len - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
@@ -265,39 +266,29 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,
|
||||
return written ? written : status;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
static int at25_regmap_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
struct at25_data *at25 = context;
|
||||
const char *buf;
|
||||
u32 offset;
|
||||
size_t len;
|
||||
int err;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
memcpy(&offset, data, sizeof(offset));
|
||||
buf = (const char *)data + sizeof(offset);
|
||||
len = count - sizeof(offset);
|
||||
|
||||
return at25_ee_write(at25, buf, off, count);
|
||||
err = at25_ee_write(at25, buf, offset, len);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Let in-kernel code access the eeprom data. */
|
||||
|
||||
static ssize_t at25_mem_read(struct memory_accessor *mem, char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at25_data *at25 = container_of(mem, struct at25_data, mem);
|
||||
|
||||
return at25_ee_read(at25, buf, offset, count);
|
||||
}
|
||||
|
||||
static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at25_data *at25 = container_of(mem, struct at25_data, mem);
|
||||
|
||||
return at25_ee_write(at25, buf, offset, count);
|
||||
}
|
||||
static const struct regmap_bus at25_regmap_bus = {
|
||||
.read = at25_regmap_read,
|
||||
.write = at25_regmap_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
@@ -358,6 +349,7 @@ static int at25_probe(struct spi_device *spi)
|
||||
{
|
||||
struct at25_data *at25 = NULL;
|
||||
struct spi_eeprom chip;
|
||||
struct regmap *regmap;
|
||||
int err;
|
||||
int sr;
|
||||
int addrlen;
|
||||
@@ -402,40 +394,35 @@ static int at25_probe(struct spi_device *spi)
|
||||
spi_set_drvdata(spi, at25);
|
||||
at25->addrlen = addrlen;
|
||||
|
||||
/* Export the EEPROM bytes through sysfs, since that's convenient.
|
||||
* And maybe to other kernel code; it might hold a board's Ethernet
|
||||
* address, or board-specific calibration data generated on the
|
||||
* manufacturing floor.
|
||||
*
|
||||
* Default to root-only access to the data; EEPROMs often hold data
|
||||
* that's sensitive for read and/or write, like ethernet addresses,
|
||||
* security codes, board-specific manufacturing calibrations, etc.
|
||||
*/
|
||||
sysfs_bin_attr_init(&at25->bin);
|
||||
at25->bin.attr.name = "eeprom";
|
||||
at25->bin.attr.mode = S_IRUSR;
|
||||
at25->bin.read = at25_bin_read;
|
||||
at25->mem.read = at25_mem_read;
|
||||
at25->regmap_config.reg_bits = 32;
|
||||
at25->regmap_config.val_bits = 8;
|
||||
at25->regmap_config.reg_stride = 1;
|
||||
at25->regmap_config.max_register = chip.byte_len - 1;
|
||||
|
||||
at25->bin.size = at25->chip.byte_len;
|
||||
if (!(chip.flags & EE_READONLY)) {
|
||||
at25->bin.write = at25_bin_write;
|
||||
at25->bin.attr.mode |= S_IWUSR;
|
||||
at25->mem.write = at25_mem_write;
|
||||
regmap = devm_regmap_init(&spi->dev, &at25_regmap_bus, at25,
|
||||
&at25->regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&spi->dev, "regmap init failed\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
if (err)
|
||||
return err;
|
||||
at25->nvmem_config.name = dev_name(&spi->dev);
|
||||
at25->nvmem_config.dev = &spi->dev;
|
||||
at25->nvmem_config.read_only = chip.flags & EE_READONLY;
|
||||
at25->nvmem_config.root_only = true;
|
||||
at25->nvmem_config.owner = THIS_MODULE;
|
||||
at25->nvmem_config.compat = true;
|
||||
at25->nvmem_config.base_dev = &spi->dev;
|
||||
|
||||
if (chip.setup)
|
||||
chip.setup(&at25->mem, chip.context);
|
||||
at25->nvmem = nvmem_register(&at25->nvmem_config);
|
||||
if (IS_ERR(at25->nvmem))
|
||||
return PTR_ERR(at25->nvmem);
|
||||
|
||||
dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",
|
||||
(at25->bin.size < 1024)
|
||||
? at25->bin.size
|
||||
: (at25->bin.size / 1024),
|
||||
(at25->bin.size < 1024) ? "Byte" : "KByte",
|
||||
dev_info(&spi->dev, "%d %s %s eeprom%s, pagesize %u\n",
|
||||
(chip.byte_len < 1024)
|
||||
? chip.byte_len
|
||||
: (chip.byte_len / 1024),
|
||||
(chip.byte_len < 1024) ? "Byte" : "KByte",
|
||||
at25->chip.name,
|
||||
(chip.flags & EE_READONLY) ? " (readonly)" : "",
|
||||
at25->chip.page_size);
|
||||
@@ -447,7 +434,8 @@ static int at25_remove(struct spi_device *spi)
|
||||
struct at25_data *at25;
|
||||
|
||||
at25 = spi_get_drvdata(spi);
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
nvmem_unregister(at25->nvmem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@@ -84,7 +84,7 @@ static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj));
|
||||
struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj));
|
||||
struct eeprom_data *data = i2c_get_clientdata(client);
|
||||
u8 slice;
|
||||
|
||||
|
@@ -10,12 +10,17 @@
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/eeprom_93xx46.h>
|
||||
|
||||
#define OP_START 0x4
|
||||
@@ -25,73 +30,111 @@
|
||||
#define ADDR_ERAL 0x20
|
||||
#define ADDR_EWEN 0x30
|
||||
|
||||
struct eeprom_93xx46_devtype_data {
|
||||
unsigned int quirks;
|
||||
};
|
||||
|
||||
static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = {
|
||||
.quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ |
|
||||
EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH,
|
||||
};
|
||||
|
||||
struct eeprom_93xx46_dev {
|
||||
struct spi_device *spi;
|
||||
struct eeprom_93xx46_platform_data *pdata;
|
||||
struct bin_attribute bin;
|
||||
struct mutex lock;
|
||||
struct regmap_config regmap_config;
|
||||
struct nvmem_config nvmem_config;
|
||||
struct nvmem_device *nvmem;
|
||||
int addrlen;
|
||||
int size;
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
static inline bool has_quirk_single_word_read(struct eeprom_93xx46_dev *edev)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct device *dev;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2];
|
||||
int bits, ret;
|
||||
u16 cmd_addr;
|
||||
return edev->pdata->quirks & EEPROM_93XX46_QUIRK_SINGLE_WORD_READ;
|
||||
}
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
edev = dev_get_drvdata(dev);
|
||||
static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev)
|
||||
{
|
||||
return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH;
|
||||
}
|
||||
|
||||
cmd_addr = OP_READ << edev->addrlen;
|
||||
static ssize_t
|
||||
eeprom_93xx46_read(struct eeprom_93xx46_dev *edev, char *buf,
|
||||
unsigned off, size_t count)
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= off & 0x7f;
|
||||
bits = 10;
|
||||
} else {
|
||||
cmd_addr |= off & 0x3f;
|
||||
bits = 9;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n",
|
||||
cmd_addr, edev->spi->max_speed_hz);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, sizeof(t));
|
||||
|
||||
t[0].tx_buf = (char *)&cmd_addr;
|
||||
t[0].len = 2;
|
||||
t[0].bits_per_word = bits;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
t[1].bits_per_word = 8;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
if (unlikely(off >= edev->size))
|
||||
return 0;
|
||||
if ((off + count) > edev->size)
|
||||
count = edev->size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
mutex_lock(&edev->lock);
|
||||
|
||||
if (edev->pdata->prepare)
|
||||
edev->pdata->prepare(edev);
|
||||
|
||||
ret = spi_sync(edev->spi, &m);
|
||||
/* have to wait at least Tcsl ns */
|
||||
ndelay(250);
|
||||
if (ret) {
|
||||
dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n",
|
||||
count, (int)off, ret);
|
||||
while (count) {
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2] = { { 0 } };
|
||||
u16 cmd_addr = OP_READ << edev->addrlen;
|
||||
size_t nbytes = count;
|
||||
int bits;
|
||||
int err;
|
||||
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= off & 0x7f;
|
||||
bits = 10;
|
||||
if (has_quirk_single_word_read(edev))
|
||||
nbytes = 1;
|
||||
} else {
|
||||
cmd_addr |= (off >> 1) & 0x3f;
|
||||
bits = 9;
|
||||
if (has_quirk_single_word_read(edev))
|
||||
nbytes = 2;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n",
|
||||
cmd_addr, edev->spi->max_speed_hz);
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
t[0].tx_buf = (char *)&cmd_addr;
|
||||
t[0].len = 2;
|
||||
t[0].bits_per_word = bits;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
t[1].bits_per_word = 8;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
err = spi_sync(edev->spi, &m);
|
||||
/* have to wait at least Tcsl ns */
|
||||
ndelay(250);
|
||||
|
||||
if (err) {
|
||||
dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n",
|
||||
nbytes, (int)off, err);
|
||||
ret = err;
|
||||
break;
|
||||
}
|
||||
|
||||
buf += nbytes;
|
||||
off += nbytes;
|
||||
count -= nbytes;
|
||||
ret += nbytes;
|
||||
}
|
||||
|
||||
if (edev->pdata->finish)
|
||||
edev->pdata->finish(edev);
|
||||
|
||||
mutex_unlock(&edev->lock);
|
||||
return ret ? : count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on)
|
||||
@@ -110,7 +153,13 @@ static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on)
|
||||
bits = 9;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr);
|
||||
if (has_quirk_instruction_length(edev)) {
|
||||
cmd_addr <<= 2;
|
||||
bits += 2;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "ew%s cmd 0x%04x, %d bits\n",
|
||||
is_on ? "en" : "ds", cmd_addr, bits);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(t));
|
||||
@@ -155,7 +204,7 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,
|
||||
bits = 10;
|
||||
data_len = 1;
|
||||
} else {
|
||||
cmd_addr |= off & 0x3f;
|
||||
cmd_addr |= (off >> 1) & 0x3f;
|
||||
bits = 9;
|
||||
data_len = 2;
|
||||
}
|
||||
@@ -182,16 +231,17 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
eeprom_93xx46_write(struct eeprom_93xx46_dev *edev, const char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct device *dev;
|
||||
int i, ret, step = 1;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
edev = dev_get_drvdata(dev);
|
||||
if (unlikely(off >= edev->size))
|
||||
return -EFBIG;
|
||||
if ((off + count) > edev->size)
|
||||
count = edev->size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
/* only write even number of bytes on 16-bit devices */
|
||||
if (edev->addrlen == 6) {
|
||||
@@ -228,6 +278,49 @@ eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj,
|
||||
return ret ? : count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide a regmap interface, which is registered with the NVMEM
|
||||
* framework
|
||||
*/
|
||||
static int eeprom_93xx46_regmap_read(void *context, const void *reg,
|
||||
size_t reg_size, void *val,
|
||||
size_t val_size)
|
||||
{
|
||||
struct eeprom_93xx46_dev *eeprom_93xx46 = context;
|
||||
off_t offset = *(u32 *)reg;
|
||||
int err;
|
||||
|
||||
err = eeprom_93xx46_read(eeprom_93xx46, val, offset, val_size);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_regmap_write(void *context, const void *data,
|
||||
size_t count)
|
||||
{
|
||||
struct eeprom_93xx46_dev *eeprom_93xx46 = context;
|
||||
const char *buf;
|
||||
u32 offset;
|
||||
size_t len;
|
||||
int err;
|
||||
|
||||
memcpy(&offset, data, sizeof(offset));
|
||||
buf = (const char *)data + sizeof(offset);
|
||||
len = count - sizeof(offset);
|
||||
|
||||
err = eeprom_93xx46_write(eeprom_93xx46, buf, offset, len);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_bus eeprom_93xx46_regmap_bus = {
|
||||
.read = eeprom_93xx46_regmap_read,
|
||||
.write = eeprom_93xx46_regmap_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev)
|
||||
{
|
||||
struct eeprom_93xx46_platform_data *pd = edev->pdata;
|
||||
@@ -245,6 +338,13 @@ static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev)
|
||||
bits = 9;
|
||||
}
|
||||
|
||||
if (has_quirk_instruction_length(edev)) {
|
||||
cmd_addr <<= 2;
|
||||
bits += 2;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "eral cmd 0x%04x, %d bits\n", cmd_addr, bits);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
@@ -294,12 +394,101 @@ static ssize_t eeprom_93xx46_store_erase(struct device *dev,
|
||||
}
|
||||
static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase);
|
||||
|
||||
static void select_assert(void *context)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev = context;
|
||||
|
||||
gpiod_set_value_cansleep(edev->pdata->select, 1);
|
||||
}
|
||||
|
||||
static void select_deassert(void *context)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev = context;
|
||||
|
||||
gpiod_set_value_cansleep(edev->pdata->select, 0);
|
||||
}
|
||||
|
||||
static const struct of_device_id eeprom_93xx46_of_table[] = {
|
||||
{ .compatible = "eeprom-93xx46", },
|
||||
{ .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table);
|
||||
|
||||
static int eeprom_93xx46_probe_dt(struct spi_device *spi)
|
||||
{
|
||||
const struct of_device_id *of_id =
|
||||
of_match_device(eeprom_93xx46_of_table, &spi->dev);
|
||||
struct device_node *np = spi->dev.of_node;
|
||||
struct eeprom_93xx46_platform_data *pd;
|
||||
u32 tmp;
|
||||
int gpio;
|
||||
enum of_gpio_flags of_flags;
|
||||
int ret;
|
||||
|
||||
pd = devm_kzalloc(&spi->dev, sizeof(*pd), GFP_KERNEL);
|
||||
if (!pd)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_property_read_u32(np, "data-size", &tmp);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "data-size property not found\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (tmp == 8) {
|
||||
pd->flags |= EE_ADDR8;
|
||||
} else if (tmp == 16) {
|
||||
pd->flags |= EE_ADDR16;
|
||||
} else {
|
||||
dev_err(&spi->dev, "invalid data-size (%d)\n", tmp);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(np, "read-only"))
|
||||
pd->flags |= EE_READONLY;
|
||||
|
||||
gpio = of_get_named_gpio_flags(np, "select-gpios", 0, &of_flags);
|
||||
if (gpio_is_valid(gpio)) {
|
||||
unsigned long flags =
|
||||
of_flags == OF_GPIO_ACTIVE_LOW ? GPIOF_ACTIVE_LOW : 0;
|
||||
|
||||
ret = devm_gpio_request_one(&spi->dev, gpio, flags,
|
||||
"eeprom_93xx46_select");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pd->select = gpio_to_desc(gpio);
|
||||
pd->prepare = select_assert;
|
||||
pd->finish = select_deassert;
|
||||
|
||||
gpiod_direction_output(pd->select, 0);
|
||||
}
|
||||
|
||||
if (of_id->data) {
|
||||
const struct eeprom_93xx46_devtype_data *data = of_id->data;
|
||||
|
||||
pd->quirks = data->quirks;
|
||||
}
|
||||
|
||||
spi->dev.platform_data = pd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_probe(struct spi_device *spi)
|
||||
{
|
||||
struct eeprom_93xx46_platform_data *pd;
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct regmap *regmap;
|
||||
int err;
|
||||
|
||||
if (spi->dev.of_node) {
|
||||
err = eeprom_93xx46_probe_dt(spi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
pd = spi->dev.platform_data;
|
||||
if (!pd) {
|
||||
dev_err(&spi->dev, "missing platform data\n");
|
||||
@@ -325,19 +514,34 @@ static int eeprom_93xx46_probe(struct spi_device *spi)
|
||||
edev->spi = spi_dev_get(spi);
|
||||
edev->pdata = pd;
|
||||
|
||||
sysfs_bin_attr_init(&edev->bin);
|
||||
edev->bin.attr.name = "eeprom";
|
||||
edev->bin.attr.mode = S_IRUSR;
|
||||
edev->bin.read = eeprom_93xx46_bin_read;
|
||||
edev->bin.size = 128;
|
||||
if (!(pd->flags & EE_READONLY)) {
|
||||
edev->bin.write = eeprom_93xx46_bin_write;
|
||||
edev->bin.attr.mode |= S_IWUSR;
|
||||
edev->size = 128;
|
||||
|
||||
edev->regmap_config.reg_bits = 32;
|
||||
edev->regmap_config.val_bits = 8;
|
||||
edev->regmap_config.reg_stride = 1;
|
||||
edev->regmap_config.max_register = edev->size - 1;
|
||||
|
||||
regmap = devm_regmap_init(&spi->dev, &eeprom_93xx46_regmap_bus, edev,
|
||||
&edev->regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&spi->dev, "regmap init failed\n");
|
||||
err = PTR_ERR(regmap);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin);
|
||||
if (err)
|
||||
edev->nvmem_config.name = dev_name(&spi->dev);
|
||||
edev->nvmem_config.dev = &spi->dev;
|
||||
edev->nvmem_config.read_only = pd->flags & EE_READONLY;
|
||||
edev->nvmem_config.root_only = true;
|
||||
edev->nvmem_config.owner = THIS_MODULE;
|
||||
edev->nvmem_config.compat = true;
|
||||
edev->nvmem_config.base_dev = &spi->dev;
|
||||
|
||||
edev->nvmem = nvmem_register(&edev->nvmem_config);
|
||||
if (IS_ERR(edev->nvmem)) {
|
||||
err = PTR_ERR(edev->nvmem);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev_info(&spi->dev, "%d-bit eeprom %s\n",
|
||||
(pd->flags & EE_ADDR8) ? 8 : 16,
|
||||
@@ -359,10 +563,11 @@ static int eeprom_93xx46_remove(struct spi_device *spi)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi);
|
||||
|
||||
nvmem_unregister(edev->nvmem);
|
||||
|
||||
if (!(edev->pdata->flags & EE_READONLY))
|
||||
device_remove_file(&spi->dev, &dev_attr_erase);
|
||||
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin);
|
||||
kfree(edev);
|
||||
return 0;
|
||||
}
|
||||
@@ -370,6 +575,7 @@ static int eeprom_93xx46_remove(struct spi_device *spi)
|
||||
static struct spi_driver eeprom_93xx46_driver = {
|
||||
.driver = {
|
||||
.name = "93xx46",
|
||||
.of_match_table = of_match_ptr(eeprom_93xx46_of_table),
|
||||
},
|
||||
.probe = eeprom_93xx46_probe,
|
||||
.remove = eeprom_93xx46_remove,
|
||||
|
@@ -278,7 +278,7 @@ static umode_t genwqe_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
unsigned int j;
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct genwqe_dev *cd = dev_get_drvdata(dev);
|
||||
umode_t mode = attr->mode;
|
||||
|
||||
|
@@ -34,6 +34,7 @@
|
||||
#include <linux/kref.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/time64.h>
|
||||
|
||||
/* Driver identification */
|
||||
#define DRIVER_NAME "ibmasm"
|
||||
@@ -53,9 +54,11 @@ extern int ibmasm_debug;
|
||||
|
||||
static inline char *get_timestamp(char *buf)
|
||||
{
|
||||
struct timeval now;
|
||||
do_gettimeofday(&now);
|
||||
sprintf(buf, "%lu.%lu", now.tv_sec, now.tv_usec);
|
||||
struct timespec64 now;
|
||||
|
||||
ktime_get_real_ts64(&now);
|
||||
sprintf(buf, "%llu.%.08lu", (long long)now.tv_sec,
|
||||
now.tv_nsec / NSEC_PER_USEC);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
@@ -209,7 +209,7 @@ static int lis3lv02d_i2c_remove(struct i2c_client *client)
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int lis3lv02d_i2c_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
if (!lis3->pdata || !lis3->pdata->wakeup_flags)
|
||||
@@ -219,7 +219,7 @@ static int lis3lv02d_i2c_suspend(struct device *dev)
|
||||
|
||||
static int lis3lv02d_i2c_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
/*
|
||||
@@ -238,7 +238,7 @@ static int lis3lv02d_i2c_resume(struct device *dev)
|
||||
#ifdef CONFIG_PM
|
||||
static int lis3_i2c_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
lis3lv02d_poweroff(lis3);
|
||||
@@ -247,7 +247,7 @@ static int lis3_i2c_runtime_suspend(struct device *dev)
|
||||
|
||||
static int lis3_i2c_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
lis3lv02d_poweron(lis3);
|
||||
|
@@ -92,6 +92,9 @@ enum ctype {
|
||||
CT_UNALIGNED_LOAD_STORE_WRITE,
|
||||
CT_OVERWRITE_ALLOCATION,
|
||||
CT_WRITE_AFTER_FREE,
|
||||
CT_READ_AFTER_FREE,
|
||||
CT_WRITE_BUDDY_AFTER_FREE,
|
||||
CT_READ_BUDDY_AFTER_FREE,
|
||||
CT_SOFTLOCKUP,
|
||||
CT_HARDLOCKUP,
|
||||
CT_SPINLOCKUP,
|
||||
@@ -105,6 +108,7 @@ enum ctype {
|
||||
CT_WRITE_RO,
|
||||
CT_WRITE_RO_AFTER_INIT,
|
||||
CT_WRITE_KERN,
|
||||
CT_WRAP_ATOMIC
|
||||
};
|
||||
|
||||
static char* cp_name[] = {
|
||||
@@ -130,6 +134,9 @@ static char* cp_type[] = {
|
||||
"UNALIGNED_LOAD_STORE_WRITE",
|
||||
"OVERWRITE_ALLOCATION",
|
||||
"WRITE_AFTER_FREE",
|
||||
"READ_AFTER_FREE",
|
||||
"WRITE_BUDDY_AFTER_FREE",
|
||||
"READ_BUDDY_AFTER_FREE",
|
||||
"SOFTLOCKUP",
|
||||
"HARDLOCKUP",
|
||||
"SPINLOCKUP",
|
||||
@@ -143,6 +150,7 @@ static char* cp_type[] = {
|
||||
"WRITE_RO",
|
||||
"WRITE_RO_AFTER_INIT",
|
||||
"WRITE_KERN",
|
||||
"WRAP_ATOMIC"
|
||||
};
|
||||
|
||||
static struct jprobe lkdtm;
|
||||
@@ -338,7 +346,7 @@ static noinline void corrupt_stack(void)
|
||||
memset((void *)data, 0, 64);
|
||||
}
|
||||
|
||||
static void execute_location(void *dst)
|
||||
static void noinline execute_location(void *dst)
|
||||
{
|
||||
void (*func)(void) = dst;
|
||||
|
||||
@@ -412,12 +420,109 @@ static void lkdtm_do_action(enum ctype which)
|
||||
break;
|
||||
}
|
||||
case CT_WRITE_AFTER_FREE: {
|
||||
int *base, *again;
|
||||
size_t len = 1024;
|
||||
u32 *data = kmalloc(len, GFP_KERNEL);
|
||||
/*
|
||||
* The slub allocator uses the first word to store the free
|
||||
* pointer in some configurations. Use the middle of the
|
||||
* allocation to avoid running into the freelist
|
||||
*/
|
||||
size_t offset = (len / sizeof(*base)) / 2;
|
||||
|
||||
kfree(data);
|
||||
base = kmalloc(len, GFP_KERNEL);
|
||||
pr_info("Allocated memory %p-%p\n", base, &base[offset * 2]);
|
||||
pr_info("Attempting bad write to freed memory at %p\n",
|
||||
&base[offset]);
|
||||
kfree(base);
|
||||
base[offset] = 0x0abcdef0;
|
||||
/* Attempt to notice the overwrite. */
|
||||
again = kmalloc(len, GFP_KERNEL);
|
||||
kfree(again);
|
||||
if (again != base)
|
||||
pr_info("Hmm, didn't get the same memory range.\n");
|
||||
|
||||
break;
|
||||
}
|
||||
case CT_READ_AFTER_FREE: {
|
||||
int *base, *val, saw;
|
||||
size_t len = 1024;
|
||||
/*
|
||||
* The slub allocator uses the first word to store the free
|
||||
* pointer in some configurations. Use the middle of the
|
||||
* allocation to avoid running into the freelist
|
||||
*/
|
||||
size_t offset = (len / sizeof(*base)) / 2;
|
||||
|
||||
base = kmalloc(len, GFP_KERNEL);
|
||||
if (!base)
|
||||
break;
|
||||
|
||||
val = kmalloc(len, GFP_KERNEL);
|
||||
if (!val)
|
||||
break;
|
||||
|
||||
*val = 0x12345678;
|
||||
base[offset] = *val;
|
||||
pr_info("Value in memory before free: %x\n", base[offset]);
|
||||
|
||||
kfree(base);
|
||||
|
||||
pr_info("Attempting bad read from freed memory\n");
|
||||
saw = base[offset];
|
||||
if (saw != *val) {
|
||||
/* Good! Poisoning happened, so declare a win. */
|
||||
pr_info("Memory correctly poisoned (%x)\n", saw);
|
||||
BUG();
|
||||
}
|
||||
pr_info("Memory was not poisoned\n");
|
||||
|
||||
kfree(val);
|
||||
break;
|
||||
}
|
||||
case CT_WRITE_BUDDY_AFTER_FREE: {
|
||||
unsigned long p = __get_free_page(GFP_KERNEL);
|
||||
if (!p)
|
||||
break;
|
||||
pr_info("Writing to the buddy page before free\n");
|
||||
memset((void *)p, 0x3, PAGE_SIZE);
|
||||
free_page(p);
|
||||
schedule();
|
||||
memset(data, 0x78, len);
|
||||
pr_info("Attempting bad write to the buddy page after free\n");
|
||||
memset((void *)p, 0x78, PAGE_SIZE);
|
||||
/* Attempt to notice the overwrite. */
|
||||
p = __get_free_page(GFP_KERNEL);
|
||||
free_page(p);
|
||||
schedule();
|
||||
|
||||
break;
|
||||
}
|
||||
case CT_READ_BUDDY_AFTER_FREE: {
|
||||
unsigned long p = __get_free_page(GFP_KERNEL);
|
||||
int saw, *val = kmalloc(1024, GFP_KERNEL);
|
||||
int *base;
|
||||
|
||||
if (!p)
|
||||
break;
|
||||
|
||||
if (!val)
|
||||
break;
|
||||
|
||||
base = (int *)p;
|
||||
|
||||
*val = 0x12345678;
|
||||
base[0] = *val;
|
||||
pr_info("Value in memory before free: %x\n", base[0]);
|
||||
free_page(p);
|
||||
pr_info("Attempting to read from freed memory\n");
|
||||
saw = base[0];
|
||||
if (saw != *val) {
|
||||
/* Good! Poisoning happened, so declare a win. */
|
||||
pr_info("Memory correctly poisoned (%x)\n", saw);
|
||||
BUG();
|
||||
}
|
||||
pr_info("Buddy page was not poisoned\n");
|
||||
|
||||
kfree(val);
|
||||
break;
|
||||
}
|
||||
case CT_SOFTLOCKUP:
|
||||
@@ -548,6 +653,17 @@ static void lkdtm_do_action(enum ctype which)
|
||||
do_overwritten();
|
||||
break;
|
||||
}
|
||||
case CT_WRAP_ATOMIC: {
|
||||
atomic_t under = ATOMIC_INIT(INT_MIN);
|
||||
atomic_t over = ATOMIC_INIT(INT_MAX);
|
||||
|
||||
pr_info("attempting atomic underflow\n");
|
||||
atomic_dec(&under);
|
||||
pr_info("attempting atomic overflow\n");
|
||||
atomic_inc(&over);
|
||||
|
||||
return;
|
||||
}
|
||||
case CT_NONE:
|
||||
default:
|
||||
break;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
config INTEL_MEI
|
||||
tristate "Intel Management Engine Interface"
|
||||
depends on X86 && PCI && WATCHDOG_CORE
|
||||
depends on X86 && PCI
|
||||
help
|
||||
The Intel Management Engine (Intel ME) provides Manageability,
|
||||
Security and Media services for system containing Intel chipsets.
|
||||
@@ -12,7 +12,7 @@ config INTEL_MEI
|
||||
config INTEL_MEI_ME
|
||||
tristate "ME Enabled Intel Chipsets"
|
||||
select INTEL_MEI
|
||||
depends on X86 && PCI && WATCHDOG_CORE
|
||||
depends on X86 && PCI
|
||||
help
|
||||
MEI support for ME Enabled Intel chipsets.
|
||||
|
||||
@@ -37,7 +37,7 @@ config INTEL_MEI_ME
|
||||
config INTEL_MEI_TXE
|
||||
tristate "Intel Trusted Execution Environment with ME Interface"
|
||||
select INTEL_MEI
|
||||
depends on X86 && PCI && WATCHDOG_CORE
|
||||
depends on X86 && PCI
|
||||
help
|
||||
MEI Support for Trusted Execution Environment device on Intel SoCs
|
||||
|
||||
|
@@ -9,7 +9,6 @@ mei-objs += interrupt.o
|
||||
mei-objs += client.o
|
||||
mei-objs += main.o
|
||||
mei-objs += amthif.o
|
||||
mei-objs += wd.o
|
||||
mei-objs += bus.o
|
||||
mei-objs += bus-fixup.o
|
||||
mei-$(CONFIG_DEBUG_FS) += debugfs.o
|
||||
|
@@ -50,7 +50,6 @@ void mei_amthif_reset_params(struct mei_device *dev)
|
||||
dev->iamthif_current_cb = NULL;
|
||||
dev->iamthif_canceled = false;
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
dev->iamthif_timer = 0;
|
||||
dev->iamthif_stall_timer = 0;
|
||||
dev->iamthif_open_count = 0;
|
||||
}
|
||||
@@ -68,11 +67,14 @@ int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
|
||||
struct mei_cl *cl = &dev->iamthif_cl;
|
||||
int ret;
|
||||
|
||||
if (mei_cl_is_connected(cl))
|
||||
return 0;
|
||||
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
|
||||
mei_cl_init(cl, dev);
|
||||
|
||||
ret = mei_cl_link(cl, MEI_IAMTHIF_HOST_CLIENT_ID);
|
||||
ret = mei_cl_link(cl);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "amthif: failed cl_link %d\n", ret);
|
||||
return ret;
|
||||
@@ -80,31 +82,9 @@ int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
|
||||
|
||||
ret = mei_cl_connect(cl, me_cl, NULL);
|
||||
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_amthif_find_read_list_entry - finds a amthilist entry for current file
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @file: pointer to file object
|
||||
*
|
||||
* Return: returned a list entry on success, NULL on failure.
|
||||
*/
|
||||
struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev,
|
||||
struct file *file)
|
||||
{
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
list_for_each_entry(cb, &dev->amthif_rd_complete_list.list, list)
|
||||
if (cb->file_object == file)
|
||||
return cb;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mei_amthif_read - read data from AMTHIF client
|
||||
*
|
||||
@@ -126,18 +106,11 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
||||
{
|
||||
struct mei_cl *cl = file->private_data;
|
||||
struct mei_cl_cb *cb;
|
||||
unsigned long timeout;
|
||||
int rets;
|
||||
int wait_ret;
|
||||
|
||||
/* Only possible if we are in timeout */
|
||||
if (!cl) {
|
||||
dev_err(dev->dev, "bad file ext.\n");
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "checking amthif data\n");
|
||||
cb = mei_amthif_find_read_list_entry(dev, file);
|
||||
cb = mei_cl_read_cb(cl, file);
|
||||
|
||||
/* Check for if we can block or not*/
|
||||
if (cb == NULL && file->f_flags & O_NONBLOCK)
|
||||
@@ -149,8 +122,9 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
||||
/* unlock the Mutex */
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
wait_ret = wait_event_interruptible(dev->iamthif_cl.wait,
|
||||
(cb = mei_amthif_find_read_list_entry(dev, file)));
|
||||
wait_ret = wait_event_interruptible(cl->rx_wait,
|
||||
!list_empty(&cl->rd_completed) ||
|
||||
!mei_cl_is_connected(cl));
|
||||
|
||||
/* Locking again the Mutex */
|
||||
mutex_lock(&dev->device_lock);
|
||||
@@ -158,7 +132,12 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
||||
if (wait_ret)
|
||||
return -ERESTARTSYS;
|
||||
|
||||
dev_dbg(dev->dev, "woke up from sleep\n");
|
||||
if (!mei_cl_is_connected(cl)) {
|
||||
rets = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
cb = mei_cl_read_cb(cl, file);
|
||||
}
|
||||
|
||||
if (cb->status) {
|
||||
@@ -168,24 +147,10 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "Got amthif data\n");
|
||||
dev->iamthif_timer = 0;
|
||||
|
||||
timeout = cb->read_time +
|
||||
mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER);
|
||||
dev_dbg(dev->dev, "amthif timeout = %lud\n",
|
||||
timeout);
|
||||
|
||||
if (time_after(jiffies, timeout)) {
|
||||
dev_dbg(dev->dev, "amthif Time out\n");
|
||||
/* 15 sec for the message has expired */
|
||||
list_del_init(&cb->list);
|
||||
rets = -ETIME;
|
||||
goto free;
|
||||
}
|
||||
/* if the whole message will fit remove it from the list */
|
||||
if (cb->buf_idx >= *offset && length >= (cb->buf_idx - *offset))
|
||||
list_del_init(&cb->list);
|
||||
else if (cb->buf_idx > 0 && cb->buf_idx <= *offset) {
|
||||
else if (cb->buf_idx <= *offset) {
|
||||
/* end of the message has been reached */
|
||||
list_del_init(&cb->list);
|
||||
rets = 0;
|
||||
@@ -195,9 +160,8 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
||||
* remove message from deletion list
|
||||
*/
|
||||
|
||||
dev_dbg(dev->dev, "amthif cb->buf size - %d\n",
|
||||
cb->buf.size);
|
||||
dev_dbg(dev->dev, "amthif cb->buf_idx - %lu\n", cb->buf_idx);
|
||||
dev_dbg(dev->dev, "amthif cb->buf.size - %zu cb->buf_idx - %zu\n",
|
||||
cb->buf.size, cb->buf_idx);
|
||||
|
||||
/* length is being truncated to PAGE_SIZE, however,
|
||||
* the buf_idx may point beyond */
|
||||
@@ -229,7 +193,7 @@ out:
|
||||
*
|
||||
* Return: 0 on success, <0 on failure.
|
||||
*/
|
||||
static int mei_amthif_read_start(struct mei_cl *cl, struct file *file)
|
||||
static int mei_amthif_read_start(struct mei_cl *cl, const struct file *file)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
struct mei_cl_cb *cb;
|
||||
@@ -248,7 +212,7 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file)
|
||||
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
|
||||
|
||||
dev->iamthif_state = MEI_IAMTHIF_READING;
|
||||
dev->iamthif_file_object = cb->file_object;
|
||||
dev->iamthif_fp = cb->fp;
|
||||
dev->iamthif_current_cb = cb;
|
||||
|
||||
return 0;
|
||||
@@ -277,7 +241,7 @@ static int mei_amthif_send_cmd(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||
|
||||
dev->iamthif_state = MEI_IAMTHIF_WRITING;
|
||||
dev->iamthif_current_cb = cb;
|
||||
dev->iamthif_file_object = cb->file_object;
|
||||
dev->iamthif_fp = cb->fp;
|
||||
dev->iamthif_canceled = false;
|
||||
|
||||
ret = mei_cl_write(cl, cb, false);
|
||||
@@ -285,7 +249,7 @@ static int mei_amthif_send_cmd(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||
return ret;
|
||||
|
||||
if (cb->completed)
|
||||
cb->status = mei_amthif_read_start(cl, cb->file_object);
|
||||
cb->status = mei_amthif_read_start(cl, cb->fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -304,8 +268,7 @@ int mei_amthif_run_next_cmd(struct mei_device *dev)
|
||||
|
||||
dev->iamthif_canceled = false;
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
dev->iamthif_timer = 0;
|
||||
dev->iamthif_file_object = NULL;
|
||||
dev->iamthif_fp = NULL;
|
||||
|
||||
dev_dbg(dev->dev, "complete amthif cmd_list cb.\n");
|
||||
|
||||
@@ -329,17 +292,17 @@ int mei_amthif_run_next_cmd(struct mei_device *dev)
|
||||
int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||
{
|
||||
|
||||
struct mei_device *dev;
|
||||
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
return -ENODEV;
|
||||
|
||||
if (WARN_ON(!cb))
|
||||
return -EINVAL;
|
||||
|
||||
dev = cl->dev;
|
||||
struct mei_device *dev = cl->dev;
|
||||
|
||||
list_add_tail(&cb->list, &dev->amthif_cmd_list.list);
|
||||
|
||||
/*
|
||||
* The previous request is still in processing, queue this one.
|
||||
*/
|
||||
if (dev->iamthif_state > MEI_IAMTHIF_IDLE &&
|
||||
dev->iamthif_state < MEI_IAMTHIF_READ_COMPLETE)
|
||||
return 0;
|
||||
|
||||
return mei_amthif_run_next_cmd(dev);
|
||||
}
|
||||
|
||||
@@ -360,10 +323,10 @@ unsigned int mei_amthif_poll(struct mei_device *dev,
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
|
||||
poll_wait(file, &dev->iamthif_cl.wait, wait);
|
||||
poll_wait(file, &dev->iamthif_cl.rx_wait, wait);
|
||||
|
||||
if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE &&
|
||||
dev->iamthif_file_object == file) {
|
||||
dev->iamthif_fp == file) {
|
||||
|
||||
mask |= POLLIN | POLLRDNORM;
|
||||
mei_amthif_run_next_cmd(dev);
|
||||
@@ -393,7 +356,7 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
return ret;
|
||||
|
||||
if (cb->completed)
|
||||
cb->status = mei_amthif_read_start(cl, cb->file_object);
|
||||
cb->status = mei_amthif_read_start(cl, cb->fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -437,11 +400,12 @@ int mei_amthif_irq_read_msg(struct mei_cl *cl,
|
||||
/**
|
||||
* mei_amthif_complete - complete amthif callback.
|
||||
*
|
||||
* @dev: the device structure.
|
||||
* @cl: host client
|
||||
* @cb: callback block.
|
||||
*/
|
||||
void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb)
|
||||
void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
|
||||
if (cb->fop_type == MEI_FOP_WRITE) {
|
||||
if (!cb->status) {
|
||||
@@ -453,25 +417,22 @@ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb)
|
||||
* in case of error enqueue the write cb to complete read list
|
||||
* so it can be propagated to the reader
|
||||
*/
|
||||
list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list);
|
||||
wake_up_interruptible(&dev->iamthif_cl.wait);
|
||||
list_add_tail(&cb->list, &cl->rd_completed);
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dev->iamthif_canceled) {
|
||||
dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE;
|
||||
dev->iamthif_stall_timer = 0;
|
||||
list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list);
|
||||
list_add_tail(&cb->list, &cl->rd_completed);
|
||||
dev_dbg(dev->dev, "amthif read completed\n");
|
||||
dev->iamthif_timer = jiffies;
|
||||
dev_dbg(dev->dev, "dev->iamthif_timer = %ld\n",
|
||||
dev->iamthif_timer);
|
||||
} else {
|
||||
mei_amthif_run_next_cmd(dev);
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "completing amthif call back.\n");
|
||||
wake_up_interruptible(&dev->iamthif_cl.wait);
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,7 +458,7 @@ static bool mei_clear_list(struct mei_device *dev,
|
||||
/* list all list member */
|
||||
list_for_each_entry_safe(cb, next, mei_cb_list, list) {
|
||||
/* check if list member associated with a file */
|
||||
if (file == cb->file_object) {
|
||||
if (file == cb->fp) {
|
||||
/* check if cb equal to current iamthif cb */
|
||||
if (dev->iamthif_current_cb == cb) {
|
||||
dev->iamthif_current_cb = NULL;
|
||||
@@ -523,13 +484,14 @@ static bool mei_clear_list(struct mei_device *dev,
|
||||
*
|
||||
* Return: true if callback removed from the list, false otherwise
|
||||
*/
|
||||
static bool mei_clear_lists(struct mei_device *dev, struct file *file)
|
||||
static bool mei_clear_lists(struct mei_device *dev, const struct file *file)
|
||||
{
|
||||
bool removed = false;
|
||||
struct mei_cl *cl = &dev->iamthif_cl;
|
||||
|
||||
/* remove callbacks associated with a file */
|
||||
mei_clear_list(dev, file, &dev->amthif_cmd_list.list);
|
||||
if (mei_clear_list(dev, file, &dev->amthif_rd_complete_list.list))
|
||||
if (mei_clear_list(dev, file, &cl->rd_completed))
|
||||
removed = true;
|
||||
|
||||
mei_clear_list(dev, file, &dev->ctrl_rd_list.list);
|
||||
@@ -546,7 +508,7 @@ static bool mei_clear_lists(struct mei_device *dev, struct file *file)
|
||||
/* check if iamthif_current_cb not NULL */
|
||||
if (dev->iamthif_current_cb && !removed) {
|
||||
/* check file and iamthif current cb association */
|
||||
if (dev->iamthif_current_cb->file_object == file) {
|
||||
if (dev->iamthif_current_cb->fp == file) {
|
||||
/* remove cb */
|
||||
mei_io_cb_free(dev->iamthif_current_cb);
|
||||
dev->iamthif_current_cb = NULL;
|
||||
@@ -569,7 +531,7 @@ int mei_amthif_release(struct mei_device *dev, struct file *file)
|
||||
if (dev->iamthif_open_count > 0)
|
||||
dev->iamthif_open_count--;
|
||||
|
||||
if (dev->iamthif_file_object == file &&
|
||||
if (dev->iamthif_fp == file &&
|
||||
dev->iamthif_state != MEI_IAMTHIF_IDLE) {
|
||||
|
||||
dev_dbg(dev->dev, "amthif canceled iamthif state %d\n",
|
||||
|
@@ -35,6 +35,9 @@ 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_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \
|
||||
0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
|
||||
|
||||
#define MEI_UUID_ANY NULL_UUID_LE
|
||||
|
||||
/**
|
||||
@@ -48,8 +51,7 @@ static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO;
|
||||
*/
|
||||
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));
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
|
||||
if (cldev->me_cl->props.max_number_of_connections > 1)
|
||||
cldev->do_match = 0;
|
||||
@@ -62,11 +64,36 @@ static void number_of_connections(struct mei_cl_device *cldev)
|
||||
*/
|
||||
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));
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
|
||||
cldev->do_match = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd - wd client on the bus, change protocol version
|
||||
* as the API has changed.
|
||||
*
|
||||
* @cldev: me clients device
|
||||
*/
|
||||
#if IS_ENABLED(CONFIG_INTEL_MEI_ME)
|
||||
#include <linux/pci.h>
|
||||
#include "hw-me-regs.h"
|
||||
static void mei_wd(struct mei_cl_device *cldev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(cldev->dev.parent);
|
||||
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
if (pdev->device == MEI_DEV_ID_WPT_LP ||
|
||||
pdev->device == MEI_DEV_ID_SPT ||
|
||||
pdev->device == MEI_DEV_ID_SPT_H)
|
||||
cldev->me_cl->props.protocol_version = 0x2;
|
||||
|
||||
cldev->do_match = 1;
|
||||
}
|
||||
#else
|
||||
static inline void mei_wd(struct mei_cl_device *cldev) {}
|
||||
#endif /* CONFIG_INTEL_MEI_ME */
|
||||
|
||||
struct mei_nfc_cmd {
|
||||
u8 command;
|
||||
u8 status;
|
||||
@@ -208,12 +235,11 @@ static void mei_nfc(struct mei_cl_device *cldev)
|
||||
|
||||
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);
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
/* we need to connect to INFO GUID */
|
||||
cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY);
|
||||
cl = mei_cl_alloc_linked(bus);
|
||||
if (IS_ERR(cl)) {
|
||||
ret = PTR_ERR(cl);
|
||||
cl = NULL;
|
||||
@@ -282,6 +308,7 @@ static struct mei_fixup {
|
||||
MEI_FIXUP(MEI_UUID_ANY, number_of_connections),
|
||||
MEI_FIXUP(MEI_UUID_NFC_INFO, blacklist),
|
||||
MEI_FIXUP(MEI_UUID_NFC_HCI, mei_nfc),
|
||||
MEI_FIXUP(MEI_UUID_WD, mei_wd),
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -44,7 +44,7 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
|
||||
bool blocking)
|
||||
{
|
||||
struct mei_device *bus;
|
||||
struct mei_cl_cb *cb = NULL;
|
||||
struct mei_cl_cb *cb;
|
||||
ssize_t rets;
|
||||
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
@@ -53,6 +53,11 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
|
||||
bus = cl->dev;
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
if (bus->dev_state != MEI_DEV_ENABLED) {
|
||||
rets = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!mei_cl_is_connected(cl)) {
|
||||
rets = -ENODEV;
|
||||
goto out;
|
||||
@@ -81,8 +86,6 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
|
||||
|
||||
out:
|
||||
mutex_unlock(&bus->device_lock);
|
||||
if (rets < 0)
|
||||
mei_io_cb_free(cb);
|
||||
|
||||
return rets;
|
||||
}
|
||||
@@ -109,6 +112,10 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length)
|
||||
bus = cl->dev;
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
if (bus->dev_state != MEI_DEV_ENABLED) {
|
||||
rets = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
cb = mei_cl_read_cb(cl, NULL);
|
||||
if (cb)
|
||||
@@ -230,45 +237,55 @@ static void mei_cl_bus_event_work(struct work_struct *work)
|
||||
* mei_cl_bus_notify_event - schedule notify cb on bus client
|
||||
*
|
||||
* @cl: host client
|
||||
*
|
||||
* Return: true if event was scheduled
|
||||
* false if the client is not waiting for event
|
||||
*/
|
||||
void mei_cl_bus_notify_event(struct mei_cl *cl)
|
||||
bool mei_cl_bus_notify_event(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_cl_device *cldev = cl->cldev;
|
||||
|
||||
if (!cldev || !cldev->event_cb)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (!(cldev->events_mask & BIT(MEI_CL_EVENT_NOTIF)))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (!cl->notify_ev)
|
||||
return;
|
||||
return false;
|
||||
|
||||
set_bit(MEI_CL_EVENT_NOTIF, &cldev->events);
|
||||
|
||||
schedule_work(&cldev->event_work);
|
||||
|
||||
cl->notify_ev = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_bus_rx_event - schedule rx evenet
|
||||
* mei_cl_bus_rx_event - schedule rx event
|
||||
*
|
||||
* @cl: host client
|
||||
*
|
||||
* Return: true if event was scheduled
|
||||
* false if the client is not waiting for event
|
||||
*/
|
||||
void mei_cl_bus_rx_event(struct mei_cl *cl)
|
||||
bool mei_cl_bus_rx_event(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_cl_device *cldev = cl->cldev;
|
||||
|
||||
if (!cldev || !cldev->event_cb)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (!(cldev->events_mask & BIT(MEI_CL_EVENT_RX)))
|
||||
return;
|
||||
return false;
|
||||
|
||||
set_bit(MEI_CL_EVENT_RX, &cldev->events);
|
||||
|
||||
schedule_work(&cldev->event_work);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,7 +415,7 @@ int mei_cldev_enable(struct mei_cl_device *cldev)
|
||||
|
||||
if (!cl) {
|
||||
mutex_lock(&bus->device_lock);
|
||||
cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY);
|
||||
cl = mei_cl_alloc_linked(bus);
|
||||
mutex_unlock(&bus->device_lock);
|
||||
if (IS_ERR(cl))
|
||||
return PTR_ERR(cl);
|
||||
@@ -958,6 +975,22 @@ void mei_cl_bus_rescan(struct mei_device *bus)
|
||||
dev_dbg(bus->dev, "rescan end");
|
||||
}
|
||||
|
||||
void mei_cl_bus_rescan_work(struct work_struct *work)
|
||||
{
|
||||
struct mei_device *bus =
|
||||
container_of(work, struct mei_device, bus_rescan_work);
|
||||
struct mei_me_client *me_cl;
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
me_cl = mei_me_cl_by_uuid(bus, &mei_amthif_guid);
|
||||
if (me_cl)
|
||||
mei_amthif_host_init(bus, me_cl);
|
||||
mei_me_cl_put(me_cl);
|
||||
mutex_unlock(&bus->device_lock);
|
||||
|
||||
mei_cl_bus_rescan(bus);
|
||||
}
|
||||
|
||||
int __mei_cldev_driver_register(struct mei_cl_driver *cldrv,
|
||||
struct module *owner)
|
||||
{
|
||||
|
@@ -359,7 +359,7 @@ void mei_io_cb_free(struct mei_cl_cb *cb)
|
||||
* Return: mei_cl_cb pointer or NULL;
|
||||
*/
|
||||
struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
|
||||
struct file *fp)
|
||||
const struct file *fp)
|
||||
{
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
@@ -368,7 +368,7 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
|
||||
return NULL;
|
||||
|
||||
INIT_LIST_HEAD(&cb->list);
|
||||
cb->file_object = fp;
|
||||
cb->fp = fp;
|
||||
cb->cl = cl;
|
||||
cb->buf_idx = 0;
|
||||
cb->fop_type = type;
|
||||
@@ -455,7 +455,8 @@ int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length)
|
||||
* Return: cb on success and NULL on failure
|
||||
*/
|
||||
struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
|
||||
enum mei_cb_file_ops type, struct file *fp)
|
||||
enum mei_cb_file_ops type,
|
||||
const struct file *fp)
|
||||
{
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
@@ -485,7 +486,7 @@ struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl, const struct file *fp)
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
list_for_each_entry(cb, &cl->rd_completed, list)
|
||||
if (!fp || fp == cb->file_object)
|
||||
if (!fp || fp == cb->fp)
|
||||
return cb;
|
||||
|
||||
return NULL;
|
||||
@@ -503,12 +504,12 @@ void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp)
|
||||
struct mei_cl_cb *cb, *next;
|
||||
|
||||
list_for_each_entry_safe(cb, next, &cl->rd_completed, list)
|
||||
if (!fp || fp == cb->file_object)
|
||||
if (!fp || fp == cb->fp)
|
||||
mei_io_cb_free(cb);
|
||||
|
||||
|
||||
list_for_each_entry_safe(cb, next, &cl->rd_pending, list)
|
||||
if (!fp || fp == cb->file_object)
|
||||
if (!fp || fp == cb->fp)
|
||||
mei_io_cb_free(cb);
|
||||
}
|
||||
|
||||
@@ -535,7 +536,6 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
|
||||
mei_io_list_flush(&cl->dev->ctrl_wr_list, cl);
|
||||
mei_io_list_flush(&cl->dev->ctrl_rd_list, cl);
|
||||
mei_io_list_flush(&cl->dev->amthif_cmd_list, cl);
|
||||
mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl);
|
||||
|
||||
mei_cl_read_cb_flush(cl, fp);
|
||||
|
||||
@@ -587,27 +587,23 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev)
|
||||
* mei_cl_link - allocate host id in the host map
|
||||
*
|
||||
* @cl: host client
|
||||
* @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one
|
||||
*
|
||||
* Return: 0 on success
|
||||
* -EINVAL on incorrect values
|
||||
* -EMFILE if open count exceeded.
|
||||
*/
|
||||
int mei_cl_link(struct mei_cl *cl, int id)
|
||||
int mei_cl_link(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
long open_handle_count;
|
||||
int id;
|
||||
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
return -EINVAL;
|
||||
|
||||
dev = cl->dev;
|
||||
|
||||
/* If Id is not assigned get one*/
|
||||
if (id == MEI_HOST_CLIENT_ID_ANY)
|
||||
id = find_first_zero_bit(dev->host_clients_map,
|
||||
MEI_CLIENTS_MAX);
|
||||
|
||||
id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX);
|
||||
if (id >= MEI_CLIENTS_MAX) {
|
||||
dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX);
|
||||
return -EMFILE;
|
||||
@@ -648,7 +644,7 @@ int mei_cl_unlink(struct mei_cl *cl)
|
||||
if (!cl)
|
||||
return 0;
|
||||
|
||||
/* wd and amthif might not be initialized */
|
||||
/* amthif might not be initialized */
|
||||
if (!cl->dev)
|
||||
return 0;
|
||||
|
||||
@@ -670,31 +666,12 @@ int mei_cl_unlink(struct mei_cl *cl)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void mei_host_client_init(struct work_struct *work)
|
||||
void mei_host_client_init(struct mei_device *dev)
|
||||
{
|
||||
struct mei_device *dev =
|
||||
container_of(work, struct mei_device, init_work);
|
||||
struct mei_me_client *me_cl;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
|
||||
me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid);
|
||||
if (me_cl)
|
||||
mei_amthif_host_init(dev, me_cl);
|
||||
mei_me_cl_put(me_cl);
|
||||
|
||||
me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid);
|
||||
if (me_cl)
|
||||
mei_wd_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);
|
||||
schedule_work(&dev->bus_rescan_work);
|
||||
|
||||
pm_runtime_mark_last_busy(dev->dev);
|
||||
dev_dbg(dev->dev, "rpm: autosuspend\n");
|
||||
@@ -725,6 +702,33 @@ bool mei_hbuf_acquire(struct mei_device *dev)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_wake_all - wake up readers, writers and event waiters so
|
||||
* they can be interrupted
|
||||
*
|
||||
* @cl: host client
|
||||
*/
|
||||
static void mei_cl_wake_all(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
|
||||
/* synchronized under device mutex */
|
||||
if (waitqueue_active(&cl->rx_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up reading client!\n");
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
}
|
||||
/* synchronized under device mutex */
|
||||
if (waitqueue_active(&cl->tx_wait)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_set_disconnected - set disconnected state and clear
|
||||
* associated states and resources
|
||||
@@ -740,8 +744,11 @@ void mei_cl_set_disconnected(struct mei_cl *cl)
|
||||
return;
|
||||
|
||||
cl->state = MEI_FILE_DISCONNECTED;
|
||||
mei_io_list_free(&dev->write_list, cl);
|
||||
mei_io_list_free(&dev->write_waiting_list, cl);
|
||||
mei_io_list_flush(&dev->ctrl_rd_list, cl);
|
||||
mei_io_list_flush(&dev->ctrl_wr_list, cl);
|
||||
mei_cl_wake_all(cl);
|
||||
cl->mei_flow_ctrl_creds = 0;
|
||||
cl->timer_count = 0;
|
||||
|
||||
@@ -1034,7 +1041,7 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
* Return: 0 on success, <0 on failure.
|
||||
*/
|
||||
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
||||
struct file *file)
|
||||
const struct file *file)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl_cb *cb;
|
||||
@@ -1119,11 +1126,10 @@ nortpm:
|
||||
* mei_cl_alloc_linked - allocate and link host client
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one
|
||||
*
|
||||
* Return: cl on success ERR_PTR on failure
|
||||
*/
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id)
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev)
|
||||
{
|
||||
struct mei_cl *cl;
|
||||
int ret;
|
||||
@@ -1134,7 +1140,7 @@ struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id)
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = mei_cl_link(cl, id);
|
||||
ret = mei_cl_link(cl);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
@@ -1149,11 +1155,12 @@ err:
|
||||
/**
|
||||
* mei_cl_flow_ctrl_creds - checks flow_control credits for cl.
|
||||
*
|
||||
* @cl: private data of the file object
|
||||
* @cl: host client
|
||||
* @fp: the file pointer associated with the pointer
|
||||
*
|
||||
* Return: 1 if mei_flow_ctrl_creds >0, 0 - otherwise.
|
||||
*/
|
||||
int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
|
||||
static int mei_cl_flow_ctrl_creds(struct mei_cl *cl, const struct file *fp)
|
||||
{
|
||||
int rets;
|
||||
|
||||
@@ -1164,7 +1171,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
|
||||
return 1;
|
||||
|
||||
if (mei_cl_is_fixed_address(cl)) {
|
||||
rets = mei_cl_read_start(cl, mei_cl_mtu(cl), NULL);
|
||||
rets = mei_cl_read_start(cl, mei_cl_mtu(cl), fp);
|
||||
if (rets && rets != -EBUSY)
|
||||
return rets;
|
||||
return 1;
|
||||
@@ -1186,7 +1193,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
|
||||
* 0 on success
|
||||
* -EINVAL when ctrl credits are <= 0
|
||||
*/
|
||||
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
|
||||
static int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
|
||||
{
|
||||
if (WARN_ON(!cl || !cl->me_cl))
|
||||
return -EINVAL;
|
||||
@@ -1283,7 +1290,8 @@ int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
*
|
||||
* Return: 0 on such and error otherwise.
|
||||
*/
|
||||
int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request)
|
||||
int mei_cl_notify_request(struct mei_cl *cl,
|
||||
const struct file *file, u8 request)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl_cb *cb;
|
||||
@@ -1368,12 +1376,12 @@ void mei_cl_notify(struct mei_cl *cl)
|
||||
|
||||
cl_dbg(dev, cl, "notify event");
|
||||
cl->notify_ev = true;
|
||||
wake_up_interruptible_all(&cl->ev_wait);
|
||||
if (!mei_cl_bus_notify_event(cl))
|
||||
wake_up_interruptible(&cl->ev_wait);
|
||||
|
||||
if (cl->ev_async)
|
||||
kill_fasync(&cl->ev_async, SIGIO, POLL_PRI);
|
||||
|
||||
mei_cl_bus_notify_event(cl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1421,6 +1429,25 @@ out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_is_read_fc_cb - check if read cb is waiting for flow control
|
||||
* for given host client
|
||||
*
|
||||
* @cl: host client
|
||||
*
|
||||
* Return: true, if found at least one cb.
|
||||
*/
|
||||
static bool mei_cl_is_read_fc_cb(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
list_for_each_entry(cb, &dev->ctrl_wr_list.list, list)
|
||||
if (cb->fop_type == MEI_FOP_READ && cb->cl == cl)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_read_start - the start read client message function.
|
||||
*
|
||||
@@ -1430,7 +1457,7 @@ out:
|
||||
*
|
||||
* Return: 0 on success, <0 on failure.
|
||||
*/
|
||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
|
||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl_cb *cb;
|
||||
@@ -1445,7 +1472,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
|
||||
return -ENODEV;
|
||||
|
||||
/* HW currently supports only one pending read */
|
||||
if (!list_empty(&cl->rd_pending))
|
||||
if (!list_empty(&cl->rd_pending) || mei_cl_is_read_fc_cb(cl))
|
||||
return -EBUSY;
|
||||
|
||||
if (!mei_me_cl_is_active(cl->me_cl)) {
|
||||
@@ -1524,7 +1551,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
|
||||
first_chunk = cb->buf_idx == 0;
|
||||
|
||||
rets = first_chunk ? mei_cl_flow_ctrl_creds(cl) : 1;
|
||||
rets = first_chunk ? mei_cl_flow_ctrl_creds(cl, cb->fp) : 1;
|
||||
if (rets < 0)
|
||||
return rets;
|
||||
|
||||
@@ -1556,7 +1583,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
return 0;
|
||||
}
|
||||
|
||||
cl_dbg(dev, cl, "buf: size = %d idx = %lu\n",
|
||||
cl_dbg(dev, cl, "buf: size = %zu idx = %zu\n",
|
||||
cb->buf.size, cb->buf_idx);
|
||||
|
||||
rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx);
|
||||
@@ -1618,7 +1645,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
|
||||
if (rets < 0 && rets != -EINPROGRESS) {
|
||||
pm_runtime_put_noidle(dev->dev);
|
||||
cl_err(dev, cl, "rpm: get failed %d\n", rets);
|
||||
return rets;
|
||||
goto free;
|
||||
}
|
||||
|
||||
cb->buf_idx = 0;
|
||||
@@ -1630,7 +1657,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
|
||||
mei_hdr.msg_complete = 0;
|
||||
mei_hdr.internal = cb->internal;
|
||||
|
||||
rets = mei_cl_flow_ctrl_creds(cl);
|
||||
rets = mei_cl_flow_ctrl_creds(cl, cb->fp);
|
||||
if (rets < 0)
|
||||
goto err;
|
||||
|
||||
@@ -1677,7 +1704,8 @@ out:
|
||||
|
||||
mutex_unlock(&dev->device_lock);
|
||||
rets = wait_event_interruptible(cl->tx_wait,
|
||||
cl->writing_state == MEI_WRITE_COMPLETE);
|
||||
cl->writing_state == MEI_WRITE_COMPLETE ||
|
||||
(!mei_cl_is_connected(cl)));
|
||||
mutex_lock(&dev->device_lock);
|
||||
/* wait_event_interruptible returns -ERESTARTSYS */
|
||||
if (rets) {
|
||||
@@ -1685,6 +1713,10 @@ out:
|
||||
rets = -EINTR;
|
||||
goto err;
|
||||
}
|
||||
if (cl->writing_state != MEI_WRITE_COMPLETE) {
|
||||
rets = -EFAULT;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
rets = size;
|
||||
@@ -1692,6 +1724,8 @@ err:
|
||||
cl_dbg(dev, cl, "rpm: autosuspend\n");
|
||||
pm_runtime_mark_last_busy(dev->dev);
|
||||
pm_runtime_put_autosuspend(dev->dev);
|
||||
free:
|
||||
mei_io_cb_free(cb);
|
||||
|
||||
return rets;
|
||||
}
|
||||
@@ -1721,10 +1755,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||
|
||||
case MEI_FOP_READ:
|
||||
list_add_tail(&cb->list, &cl->rd_completed);
|
||||
if (waitqueue_active(&cl->rx_wait))
|
||||
wake_up_interruptible_all(&cl->rx_wait);
|
||||
else
|
||||
mei_cl_bus_rx_event(cl);
|
||||
if (!mei_cl_bus_rx_event(cl))
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
break;
|
||||
|
||||
case MEI_FOP_CONNECT:
|
||||
@@ -1753,44 +1785,3 @@ void mei_cl_all_disconnect(struct mei_device *dev)
|
||||
list_for_each_entry(cl, &dev->file_list, link)
|
||||
mei_cl_set_disconnected(cl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mei_cl_all_wakeup - wake up all readers and writers they can be interrupted
|
||||
*
|
||||
* @dev: mei device
|
||||
*/
|
||||
void mei_cl_all_wakeup(struct mei_device *dev)
|
||||
{
|
||||
struct mei_cl *cl;
|
||||
|
||||
list_for_each_entry(cl, &dev->file_list, link) {
|
||||
if (waitqueue_active(&cl->rx_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up reading client!\n");
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
}
|
||||
if (waitqueue_active(&cl->tx_wait)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_all_write_clear - clear all pending writes
|
||||
*
|
||||
* @dev: mei device
|
||||
*/
|
||||
void mei_cl_all_write_clear(struct mei_device *dev)
|
||||
{
|
||||
mei_io_list_free(&dev->write_list, NULL);
|
||||
mei_io_list_free(&dev->write_waiting_list, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -18,7 +18,6 @@
|
||||
#define _MEI_CLIENT_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/mei.h>
|
||||
|
||||
@@ -84,7 +83,7 @@ static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl)
|
||||
* MEI IO Functions
|
||||
*/
|
||||
struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
|
||||
struct file *fp);
|
||||
const struct file *fp);
|
||||
void mei_io_cb_free(struct mei_cl_cb *priv_cb);
|
||||
int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length);
|
||||
|
||||
@@ -108,21 +107,19 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev);
|
||||
void mei_cl_init(struct mei_cl *cl, struct mei_device *dev);
|
||||
|
||||
|
||||
int mei_cl_link(struct mei_cl *cl, int id);
|
||||
int mei_cl_link(struct mei_cl *cl);
|
||||
int mei_cl_unlink(struct mei_cl *cl);
|
||||
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id);
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev);
|
||||
|
||||
struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl,
|
||||
const struct file *fp);
|
||||
void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp);
|
||||
struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
|
||||
enum mei_cb_file_ops type, struct file *fp);
|
||||
enum mei_cb_file_ops type,
|
||||
const struct file *fp);
|
||||
int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp);
|
||||
|
||||
int mei_cl_flow_ctrl_creds(struct mei_cl *cl);
|
||||
|
||||
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl);
|
||||
/*
|
||||
* MEI input output function prototype
|
||||
*/
|
||||
@@ -217,10 +214,10 @@ void mei_cl_set_disconnected(struct mei_cl *cl);
|
||||
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
||||
struct file *file);
|
||||
const struct file *file);
|
||||
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp);
|
||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp);
|
||||
int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking);
|
||||
@@ -229,19 +226,18 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
|
||||
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||
|
||||
void mei_host_client_init(struct work_struct *work);
|
||||
void mei_host_client_init(struct mei_device *dev);
|
||||
|
||||
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_notify_request(struct mei_cl *cl,
|
||||
const 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);
|
||||
|
||||
#define MEI_CL_FMT "cl:host=%02d me=%02d "
|
||||
#define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl)
|
||||
@@ -249,6 +245,9 @@ void mei_cl_all_write_clear(struct mei_device *dev);
|
||||
#define cl_dbg(dev, cl, format, arg...) \
|
||||
dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
|
||||
|
||||
#define cl_warn(dev, cl, format, arg...) \
|
||||
dev_warn((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
|
||||
|
||||
#define cl_err(dev, cl, format, arg...) \
|
||||
dev_err((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
|
||||
|
||||
|
@@ -50,6 +50,7 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf,
|
||||
}
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos, HDR);
|
||||
#undef HDR
|
||||
|
||||
/* if the driver is not enabled the list won't be consistent */
|
||||
if (dev->dev_state != MEI_DEV_ENABLED)
|
||||
@@ -90,24 +91,38 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
|
||||
{
|
||||
struct mei_device *dev = fp->private_data;
|
||||
struct mei_cl *cl;
|
||||
const size_t bufsz = 1024;
|
||||
size_t bufsz = 1;
|
||||
char *buf;
|
||||
int i = 0;
|
||||
int pos = 0;
|
||||
int ret;
|
||||
|
||||
#define HDR " |me|host|state|rd|wr|\n"
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
buf = kzalloc(bufsz, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos,
|
||||
" |me|host|state|rd|wr|\n");
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
/*
|
||||
* if the driver is not enabled the list won't be consistent,
|
||||
* we output empty table
|
||||
*/
|
||||
if (dev->dev_state == MEI_DEV_ENABLED)
|
||||
list_for_each_entry(cl, &dev->file_list, link)
|
||||
bufsz++;
|
||||
|
||||
bufsz *= sizeof(HDR) + 1;
|
||||
|
||||
buf = kzalloc(bufsz, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
mutex_unlock(&dev->device_lock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos, HDR);
|
||||
#undef HDR
|
||||
|
||||
/* if the driver is not enabled the list won't be consistent */
|
||||
if (dev->dev_state != MEI_DEV_ENABLED)
|
||||
goto out;
|
||||
@@ -115,7 +130,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
|
||||
list_for_each_entry(cl, &dev->file_list, link) {
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos,
|
||||
"%2d|%2d|%4d|%5d|%2d|%2d|\n",
|
||||
"%3d|%2d|%4d|%5d|%2d|%2d|\n",
|
||||
i, mei_cl_me_id(cl), cl->host_client_id, cl->state,
|
||||
!list_empty(&cl->rd_completed), cl->writing_state);
|
||||
i++;
|
||||
@@ -150,16 +165,21 @@ static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf,
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "hbm: %s\n",
|
||||
mei_hbm_state_str(dev->hbm_state));
|
||||
|
||||
if (dev->hbm_state == MEI_HBM_STARTED) {
|
||||
if (dev->hbm_state >= MEI_HBM_ENUM_CLIENTS &&
|
||||
dev->hbm_state <= MEI_HBM_STARTED) {
|
||||
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, "\tIE: %01d\n",
|
||||
dev->hbm_f_ie_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, "\tFA: %01d\n",
|
||||
dev->hbm_f_fa_supported);
|
||||
}
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "pg: %s, %s\n",
|
||||
@@ -175,6 +195,30 @@ static const struct file_operations mei_dbgfs_fops_devstate = {
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t mei_dbgfs_write_allow_fa(struct file *file,
|
||||
const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
int ret;
|
||||
|
||||
dev = container_of(file->private_data,
|
||||
struct mei_device, allow_fixed_address);
|
||||
|
||||
ret = debugfs_write_file_bool(file, user_buf, count, ppos);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dev->override_fixed_address = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations mei_dbgfs_fops_allow_fa = {
|
||||
.open = simple_open,
|
||||
.read = debugfs_read_file_bool,
|
||||
.write = mei_dbgfs_write_allow_fa,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/**
|
||||
* mei_dbgfs_deregister - Remove the debugfs files and directories
|
||||
*
|
||||
@@ -224,8 +268,9 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name)
|
||||
dev_err(dev->dev, "devstate: registration failed\n");
|
||||
goto err;
|
||||
}
|
||||
f = debugfs_create_bool("allow_fixed_address", S_IRUSR | S_IWUSR, dir,
|
||||
&dev->allow_fixed_address);
|
||||
f = debugfs_create_file("allow_fixed_address", S_IRUSR | S_IWUSR, dir,
|
||||
&dev->allow_fixed_address,
|
||||
&mei_dbgfs_fops_allow_fa);
|
||||
if (!f) {
|
||||
dev_err(dev->dev, "allow_fixed_address: registration failed\n");
|
||||
goto err;
|
||||
|
@@ -301,7 +301,10 @@ 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;
|
||||
enum_req->flags |= dev->hbm_f_dc_supported ?
|
||||
MEI_HBM_ENUM_F_ALLOW_ADD : 0;
|
||||
enum_req->flags |= dev->hbm_f_ie_supported ?
|
||||
MEI_HBM_ENUM_F_IMMEDIATE_ENUM : 0;
|
||||
|
||||
ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data);
|
||||
if (ret) {
|
||||
@@ -401,6 +404,9 @@ static int mei_hbm_fw_add_cl_req(struct mei_device *dev,
|
||||
if (ret)
|
||||
status = !MEI_HBMS_SUCCESS;
|
||||
|
||||
if (dev->dev_state == MEI_DEV_ENABLED)
|
||||
schedule_work(&dev->bus_rescan_work);
|
||||
|
||||
return mei_hbm_add_cl_resp(dev, req->me_addr, status);
|
||||
}
|
||||
|
||||
@@ -543,7 +549,7 @@ static int mei_hbm_prop_req(struct mei_device *dev)
|
||||
/* We got all client properties */
|
||||
if (next_client_index == MEI_CLIENTS_MAX) {
|
||||
dev->hbm_state = MEI_HBM_STARTED;
|
||||
schedule_work(&dev->init_work);
|
||||
mei_host_client_init(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -789,8 +795,11 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, struct mei_cl *cl,
|
||||
cl->state = MEI_FILE_CONNECTED;
|
||||
else {
|
||||
cl->state = MEI_FILE_DISCONNECT_REPLY;
|
||||
if (rs->status == MEI_CL_CONN_NOT_FOUND)
|
||||
if (rs->status == MEI_CL_CONN_NOT_FOUND) {
|
||||
mei_me_cl_del(dev, cl->me_cl);
|
||||
if (dev->dev_state == MEI_DEV_ENABLED)
|
||||
schedule_work(&dev->bus_rescan_work);
|
||||
}
|
||||
}
|
||||
cl->status = mei_cl_conn_status_to_errno(rs->status);
|
||||
}
|
||||
@@ -866,7 +875,7 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev,
|
||||
|
||||
cl = mei_hbm_cl_find_by_cmd(dev, disconnect_req);
|
||||
if (cl) {
|
||||
cl_dbg(dev, cl, "fw disconnect request received\n");
|
||||
cl_warn(dev, cl, "fw disconnect request received\n");
|
||||
cl->state = MEI_FILE_DISCONNECTING;
|
||||
cl->timer_count = 0;
|
||||
|
||||
@@ -972,6 +981,9 @@ static void mei_hbm_config_features(struct mei_device *dev)
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_DC)
|
||||
dev->hbm_f_dc_supported = 1;
|
||||
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_IE)
|
||||
dev->hbm_f_ie_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;
|
||||
@@ -979,6 +991,10 @@ static void mei_hbm_config_features(struct mei_device *dev)
|
||||
/* Notification Event Support */
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_EV)
|
||||
dev->hbm_f_ev_supported = 1;
|
||||
|
||||
/* Fixed Address Client Support */
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_FA)
|
||||
dev->hbm_f_fa_supported = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -121,6 +121,10 @@
|
||||
#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 */
|
||||
|
||||
#define MEI_DEV_ID_BXT_M 0x1A9A /* Broxton M */
|
||||
#define MEI_DEV_ID_APL_I 0x5A9A /* Apollo Lake I */
|
||||
|
||||
/*
|
||||
* MEI HW Section
|
||||
*/
|
||||
|
@@ -189,8 +189,11 @@ static int mei_me_fw_status(struct mei_device *dev,
|
||||
|
||||
fw_status->count = fw_src->count;
|
||||
for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) {
|
||||
ret = pci_read_config_dword(pdev,
|
||||
fw_src->status[i], &fw_status->status[i]);
|
||||
ret = pci_read_config_dword(pdev, fw_src->status[i],
|
||||
&fw_status->status[i]);
|
||||
trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X",
|
||||
fw_src->status[i],
|
||||
fw_status->status[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@@ -215,6 +218,7 @@ static void mei_me_hw_config(struct mei_device *dev)
|
||||
|
||||
reg = 0;
|
||||
pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®);
|
||||
trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg);
|
||||
hw->d0i3_supported =
|
||||
((reg & PCI_CFG_HFS_1_D0I3_MSK) == PCI_CFG_HFS_1_D0I3_MSK);
|
||||
|
||||
@@ -1248,6 +1252,7 @@ static bool mei_me_fw_type_nm(struct pci_dev *pdev)
|
||||
u32 reg;
|
||||
|
||||
pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®);
|
||||
trace_mei_pci_cfg_read(&pdev->dev, "PCI_CFG_HFS_2", PCI_CFG_HFS_2, reg);
|
||||
/* make sure that bit 9 (NM) is up and bit 10 (DM) is down */
|
||||
return (reg & 0x600) == 0x200;
|
||||
}
|
||||
@@ -1260,6 +1265,7 @@ static bool mei_me_fw_type_sps(struct pci_dev *pdev)
|
||||
u32 reg;
|
||||
/* Read ME FW Status check for SPS Firmware */
|
||||
pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®);
|
||||
trace_mei_pci_cfg_read(&pdev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg);
|
||||
/* if bits [19:16] = 15, running SPS Firmware */
|
||||
return (reg & 0xf0000) == 0xf0000;
|
||||
}
|
||||
|
@@ -28,6 +28,9 @@
|
||||
#include "client.h"
|
||||
#include "hbm.h"
|
||||
|
||||
#include "mei-trace.h"
|
||||
|
||||
|
||||
/**
|
||||
* mei_txe_reg_read - Reads 32bit data from the txe device
|
||||
*
|
||||
@@ -640,8 +643,11 @@ static int mei_txe_fw_status(struct mei_device *dev,
|
||||
|
||||
fw_status->count = fw_src->count;
|
||||
for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) {
|
||||
ret = pci_read_config_dword(pdev,
|
||||
fw_src->status[i], &fw_status->status[i]);
|
||||
ret = pci_read_config_dword(pdev, fw_src->status[i],
|
||||
&fw_status->status[i]);
|
||||
trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X",
|
||||
fw_src->status[i],
|
||||
fw_status->status[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
@@ -29,7 +29,6 @@
|
||||
#define MEI_CLIENTS_INIT_TIMEOUT 15 /* HPS: Clients Enumeration Timeout */
|
||||
|
||||
#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_D0I3_TIMEOUT 5 /* D0i3 set/unset max response time */
|
||||
@@ -53,6 +52,12 @@
|
||||
#define HBM_MINOR_VERSION_DC 0
|
||||
#define HBM_MAJOR_VERSION_DC 2
|
||||
|
||||
/*
|
||||
* MEI version with immediate reply to enum request support
|
||||
*/
|
||||
#define HBM_MINOR_VERSION_IE 0
|
||||
#define HBM_MAJOR_VERSION_IE 2
|
||||
|
||||
/*
|
||||
* MEI version with disconnect on connection timeout support
|
||||
*/
|
||||
@@ -65,6 +70,12 @@
|
||||
#define HBM_MINOR_VERSION_EV 0
|
||||
#define HBM_MAJOR_VERSION_EV 2
|
||||
|
||||
/*
|
||||
* MEI version with fixed address client support
|
||||
*/
|
||||
#define HBM_MINOR_VERSION_FA 0
|
||||
#define HBM_MAJOR_VERSION_FA 2
|
||||
|
||||
/* Host bus message command opcode */
|
||||
#define MEI_HBM_CMD_OP_MSK 0x7f
|
||||
/* Host bus message command RESPONSE */
|
||||
@@ -241,15 +252,26 @@ struct hbm_me_stop_request {
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* struct hbm_host_enum_request - enumeration request from host to fw
|
||||
* enum hbm_host_enum_flags - enumeration request flags (HBM version >= 2.0)
|
||||
*
|
||||
* @hbm_cmd: bus message command header
|
||||
* @allow_add: allow dynamic clients add HBM version >= 2.0
|
||||
* @MEI_HBM_ENUM_F_ALLOW_ADD: allow dynamic clients add
|
||||
* @MEI_HBM_ENUM_F_IMMEDIATE_ENUM: allow FW to send answer immediately
|
||||
*/
|
||||
enum hbm_host_enum_flags {
|
||||
MEI_HBM_ENUM_F_ALLOW_ADD = BIT(0),
|
||||
MEI_HBM_ENUM_F_IMMEDIATE_ENUM = BIT(1),
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hbm_host_enum_request - enumeration request from host to fw
|
||||
*
|
||||
* @hbm_cmd : bus message command header
|
||||
* @flags : request flags
|
||||
* @reserved: reserved
|
||||
*/
|
||||
struct hbm_host_enum_request {
|
||||
u8 hbm_cmd;
|
||||
u8 allow_add;
|
||||
u8 flags;
|
||||
u8 reserved[2];
|
||||
} __packed;
|
||||
|
||||
|
@@ -91,8 +91,8 @@ EXPORT_SYMBOL_GPL(mei_fw_status2str);
|
||||
*/
|
||||
void mei_cancel_work(struct mei_device *dev)
|
||||
{
|
||||
cancel_work_sync(&dev->init_work);
|
||||
cancel_work_sync(&dev->reset_work);
|
||||
cancel_work_sync(&dev->bus_rescan_work);
|
||||
|
||||
cancel_delayed_work(&dev->timer_work);
|
||||
}
|
||||
@@ -148,16 +148,10 @@ int mei_reset(struct mei_device *dev)
|
||||
state != MEI_DEV_POWER_UP) {
|
||||
|
||||
/* remove all waiting requests */
|
||||
mei_cl_all_write_clear(dev);
|
||||
|
||||
mei_cl_all_disconnect(dev);
|
||||
|
||||
/* wake up all readers and writers so they can be interrupted */
|
||||
mei_cl_all_wakeup(dev);
|
||||
|
||||
/* remove entry if already in list */
|
||||
dev_dbg(dev->dev, "remove iamthif and wd from the file list.\n");
|
||||
mei_cl_unlink(&dev->wd_cl);
|
||||
dev_dbg(dev->dev, "remove iamthif from the file list.\n");
|
||||
mei_cl_unlink(&dev->iamthif_cl);
|
||||
mei_amthif_reset_params(dev);
|
||||
}
|
||||
@@ -165,7 +159,6 @@ int mei_reset(struct mei_device *dev)
|
||||
mei_hbm_reset(dev);
|
||||
|
||||
dev->rd_msg_hdr = 0;
|
||||
dev->wd_pending = false;
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "hw_reset failed ret = %d\n", ret);
|
||||
@@ -335,16 +328,12 @@ void mei_stop(struct mei_device *dev)
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
mei_wd_stop(dev);
|
||||
|
||||
dev->dev_state = MEI_DEV_POWER_DOWN;
|
||||
mei_reset(dev);
|
||||
/* move device to disabled state unconditionally */
|
||||
dev->dev_state = MEI_DEV_DISABLED;
|
||||
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
mei_watchdog_unregister(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mei_stop);
|
||||
|
||||
@@ -394,7 +383,6 @@ void mei_device_init(struct mei_device *dev,
|
||||
init_waitqueue_head(&dev->wait_hw_ready);
|
||||
init_waitqueue_head(&dev->wait_pg);
|
||||
init_waitqueue_head(&dev->wait_hbm_start);
|
||||
init_waitqueue_head(&dev->wait_stop_wd);
|
||||
dev->dev_state = MEI_DEV_INITIALIZING;
|
||||
dev->reset_count = 0;
|
||||
|
||||
@@ -404,13 +392,11 @@ void mei_device_init(struct mei_device *dev,
|
||||
mei_io_list_init(&dev->ctrl_rd_list);
|
||||
|
||||
INIT_DELAYED_WORK(&dev->timer_work, mei_timer);
|
||||
INIT_WORK(&dev->init_work, mei_host_client_init);
|
||||
INIT_WORK(&dev->reset_work, mei_reset_work);
|
||||
INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work);
|
||||
|
||||
INIT_LIST_HEAD(&dev->wd_cl.link);
|
||||
INIT_LIST_HEAD(&dev->iamthif_cl.link);
|
||||
mei_io_list_init(&dev->amthif_cmd_list);
|
||||
mei_io_list_init(&dev->amthif_rd_complete_list);
|
||||
|
||||
bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);
|
||||
dev->open_handle_count = 0;
|
||||
|
@@ -48,7 +48,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *compl_list)
|
||||
|
||||
dev_dbg(dev->dev, "completing call back.\n");
|
||||
if (cl == &dev->iamthif_cl)
|
||||
mei_amthif_complete(dev, cb);
|
||||
mei_amthif_complete(cl, cb);
|
||||
else
|
||||
mei_cl_complete(cl, cb);
|
||||
}
|
||||
@@ -104,6 +104,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||
struct mei_device *dev = cl->dev;
|
||||
struct mei_cl_cb *cb;
|
||||
unsigned char *buffer = NULL;
|
||||
size_t buf_sz;
|
||||
|
||||
cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list);
|
||||
if (!cb) {
|
||||
@@ -124,11 +125,21 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cb->buf.size < mei_hdr->length + cb->buf_idx) {
|
||||
cl_dbg(dev, cl, "message overflow. size %d len %d idx %ld\n",
|
||||
buf_sz = mei_hdr->length + cb->buf_idx;
|
||||
/* catch for integer overflow */
|
||||
if (buf_sz < cb->buf_idx) {
|
||||
cl_err(dev, cl, "message is too big len %d idx %zu\n",
|
||||
mei_hdr->length, cb->buf_idx);
|
||||
|
||||
list_move_tail(&cb->list, &complete_list->list);
|
||||
cb->status = -EMSGSIZE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cb->buf.size < buf_sz) {
|
||||
cl_dbg(dev, cl, "message overflow. size %zu len %d idx %zu\n",
|
||||
cb->buf.size, mei_hdr->length, cb->buf_idx);
|
||||
buffer = krealloc(cb->buf.data, mei_hdr->length + cb->buf_idx,
|
||||
GFP_KERNEL);
|
||||
buffer = krealloc(cb->buf.data, buf_sz, GFP_KERNEL);
|
||||
|
||||
if (!buffer) {
|
||||
cb->status = -ENOMEM;
|
||||
@@ -136,7 +147,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||
goto out;
|
||||
}
|
||||
cb->buf.data = buffer;
|
||||
cb->buf.size = mei_hdr->length + cb->buf_idx;
|
||||
cb->buf.size = buf_sz;
|
||||
}
|
||||
|
||||
buffer = cb->buf.data + cb->buf_idx;
|
||||
@@ -145,8 +156,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||
cb->buf_idx += mei_hdr->length;
|
||||
|
||||
if (mei_hdr->msg_complete) {
|
||||
cb->read_time = jiffies;
|
||||
cl_dbg(dev, cl, "completed read length = %lu\n", cb->buf_idx);
|
||||
cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx);
|
||||
list_move_tail(&cb->list, &complete_list->list);
|
||||
} else {
|
||||
pm_runtime_mark_last_busy(dev->dev);
|
||||
@@ -229,6 +239,16 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool hdr_is_hbm(struct mei_msg_hdr *mei_hdr)
|
||||
{
|
||||
return mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0;
|
||||
}
|
||||
|
||||
static inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr)
|
||||
{
|
||||
return mei_hdr->host_addr == 0 && mei_hdr->me_addr != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_irq_read_handler - bottom half read routine after ISR to
|
||||
* handle the read processing.
|
||||
@@ -270,7 +290,7 @@ int mei_irq_read_handler(struct mei_device *dev,
|
||||
}
|
||||
|
||||
/* HBM message */
|
||||
if (mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0) {
|
||||
if (hdr_is_hbm(mei_hdr)) {
|
||||
ret = mei_hbm_dispatch(dev, mei_hdr);
|
||||
if (ret) {
|
||||
dev_dbg(dev->dev, "mei_hbm_dispatch failed ret = %d\n",
|
||||
@@ -290,6 +310,14 @@ int mei_irq_read_handler(struct mei_device *dev,
|
||||
|
||||
/* if no recipient cl was found we assume corrupted header */
|
||||
if (&cl->link == &dev->file_list) {
|
||||
/* A message for not connected fixed address clients
|
||||
* should be silently discarded
|
||||
*/
|
||||
if (hdr_is_fixed(mei_hdr)) {
|
||||
mei_irq_discard_msg(dev, mei_hdr);
|
||||
ret = 0;
|
||||
goto reset_slots;
|
||||
}
|
||||
dev_err(dev->dev, "no destination client found 0x%08X\n",
|
||||
dev->rd_msg_hdr);
|
||||
ret = -EBADMSG;
|
||||
@@ -360,21 +388,6 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
|
||||
list_move_tail(&cb->list, &cmpl_list->list);
|
||||
}
|
||||
|
||||
if (dev->wd_state == MEI_WD_STOPPING) {
|
||||
dev->wd_state = MEI_WD_IDLE;
|
||||
wake_up(&dev->wait_stop_wd);
|
||||
}
|
||||
|
||||
if (mei_cl_is_connected(&dev->wd_cl)) {
|
||||
if (dev->wd_pending &&
|
||||
mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) {
|
||||
ret = mei_wd_send(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
dev->wd_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* complete control write list CB */
|
||||
dev_dbg(dev->dev, "complete control write list cb.\n");
|
||||
list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list.list, list) {
|
||||
@@ -462,7 +475,6 @@ static void mei_connect_timeout(struct mei_cl *cl)
|
||||
*/
|
||||
void mei_timer(struct work_struct *work)
|
||||
{
|
||||
unsigned long timeout;
|
||||
struct mei_cl *cl;
|
||||
|
||||
struct mei_device *dev = container_of(work,
|
||||
@@ -508,45 +520,15 @@ void mei_timer(struct work_struct *work)
|
||||
mei_reset(dev);
|
||||
dev->iamthif_canceled = false;
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
dev->iamthif_timer = 0;
|
||||
|
||||
mei_io_cb_free(dev->iamthif_current_cb);
|
||||
dev->iamthif_current_cb = NULL;
|
||||
|
||||
dev->iamthif_file_object = NULL;
|
||||
dev->iamthif_fp = NULL;
|
||||
mei_amthif_run_next_cmd(dev);
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->iamthif_timer) {
|
||||
|
||||
timeout = dev->iamthif_timer +
|
||||
mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER);
|
||||
|
||||
dev_dbg(dev->dev, "dev->iamthif_timer = %ld\n",
|
||||
dev->iamthif_timer);
|
||||
dev_dbg(dev->dev, "timeout = %ld\n", timeout);
|
||||
dev_dbg(dev->dev, "jiffies = %ld\n", jiffies);
|
||||
if (time_after(jiffies, timeout)) {
|
||||
/*
|
||||
* User didn't read the AMTHI data on time (15sec)
|
||||
* freeing AMTHI for other requests
|
||||
*/
|
||||
|
||||
dev_dbg(dev->dev, "freeing AMTHI for other requests\n");
|
||||
|
||||
mei_io_list_flush(&dev->amthif_rd_complete_list,
|
||||
&dev->iamthif_cl);
|
||||
mei_io_cb_free(dev->iamthif_current_cb);
|
||||
dev->iamthif_current_cb = NULL;
|
||||
|
||||
dev->iamthif_file_object->private_data = NULL;
|
||||
dev->iamthif_file_object = NULL;
|
||||
dev->iamthif_timer = 0;
|
||||
mei_amthif_run_next_cmd(dev);
|
||||
|
||||
}
|
||||
}
|
||||
out:
|
||||
if (dev->dev_state != MEI_DEV_DISABLED)
|
||||
schedule_delayed_work(&dev->timer_work, 2 * HZ);
|
||||
|
@@ -65,7 +65,7 @@ static int mei_open(struct inode *inode, struct file *file)
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
cl = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY);
|
||||
cl = mei_cl_alloc_linked(dev);
|
||||
if (IS_ERR(cl)) {
|
||||
err = PTR_ERR(cl);
|
||||
goto err_unlock;
|
||||
@@ -159,27 +159,22 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ubuf == NULL) {
|
||||
rets = -EMSGSIZE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cl == &dev->iamthif_cl) {
|
||||
rets = mei_amthif_read(dev, file, ubuf, length, offset);
|
||||
goto out;
|
||||
}
|
||||
|
||||
cb = mei_cl_read_cb(cl, file);
|
||||
if (cb) {
|
||||
/* read what left */
|
||||
if (cb->buf_idx > *offset)
|
||||
goto copy_buffer;
|
||||
/* offset is beyond buf_idx we have no more data return 0 */
|
||||
if (cb->buf_idx > 0 && cb->buf_idx <= *offset) {
|
||||
rets = 0;
|
||||
goto free;
|
||||
}
|
||||
/* Offset needs to be cleaned for contiguous reads*/
|
||||
if (cb->buf_idx == 0 && *offset > 0)
|
||||
*offset = 0;
|
||||
} else if (*offset > 0) {
|
||||
if (cb)
|
||||
goto copy_buffer;
|
||||
|
||||
if (*offset > 0)
|
||||
*offset = 0;
|
||||
}
|
||||
|
||||
err = mei_cl_read_start(cl, length, file);
|
||||
if (err && err != -EBUSY) {
|
||||
@@ -214,11 +209,6 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
|
||||
|
||||
cb = mei_cl_read_cb(cl, file);
|
||||
if (!cb) {
|
||||
if (mei_cl_is_fixed_address(cl) && dev->allow_fixed_address) {
|
||||
cb = mei_cl_read_cb(cl, NULL);
|
||||
if (cb)
|
||||
goto copy_buffer;
|
||||
}
|
||||
rets = 0;
|
||||
goto out;
|
||||
}
|
||||
@@ -231,10 +221,10 @@ copy_buffer:
|
||||
goto free;
|
||||
}
|
||||
|
||||
cl_dbg(dev, cl, "buf.size = %d buf.idx = %ld\n",
|
||||
cb->buf.size, cb->buf_idx);
|
||||
if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) {
|
||||
rets = -EMSGSIZE;
|
||||
cl_dbg(dev, cl, "buf.size = %zu buf.idx = %zu offset = %lld\n",
|
||||
cb->buf.size, cb->buf_idx, *offset);
|
||||
if (*offset >= cb->buf_idx) {
|
||||
rets = 0;
|
||||
goto free;
|
||||
}
|
||||
|
||||
@@ -250,11 +240,13 @@ copy_buffer:
|
||||
|
||||
rets = length;
|
||||
*offset += length;
|
||||
if ((unsigned long)*offset < cb->buf_idx)
|
||||
/* not all data was read, keep the cb */
|
||||
if (*offset < cb->buf_idx)
|
||||
goto out;
|
||||
|
||||
free:
|
||||
mei_io_cb_free(cb);
|
||||
*offset = 0;
|
||||
|
||||
out:
|
||||
cl_dbg(dev, cl, "end mei read rets = %d\n", rets);
|
||||
@@ -275,9 +267,8 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
|
||||
size_t length, loff_t *offset)
|
||||
{
|
||||
struct mei_cl *cl = file->private_data;
|
||||
struct mei_cl_cb *write_cb = NULL;
|
||||
struct mei_cl_cb *cb;
|
||||
struct mei_device *dev;
|
||||
unsigned long timeout = 0;
|
||||
int rets;
|
||||
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
@@ -313,52 +304,31 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cl == &dev->iamthif_cl) {
|
||||
write_cb = mei_amthif_find_read_list_entry(dev, file);
|
||||
|
||||
if (write_cb) {
|
||||
timeout = write_cb->read_time +
|
||||
mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER);
|
||||
|
||||
if (time_after(jiffies, timeout)) {
|
||||
*offset = 0;
|
||||
mei_io_cb_free(write_cb);
|
||||
write_cb = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*offset = 0;
|
||||
write_cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file);
|
||||
if (!write_cb) {
|
||||
cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file);
|
||||
if (!cb) {
|
||||
rets = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rets = copy_from_user(write_cb->buf.data, ubuf, length);
|
||||
rets = copy_from_user(cb->buf.data, ubuf, length);
|
||||
if (rets) {
|
||||
dev_dbg(dev->dev, "failed to copy data from userland\n");
|
||||
rets = -EFAULT;
|
||||
mei_io_cb_free(cb);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cl == &dev->iamthif_cl) {
|
||||
rets = mei_amthif_write(cl, write_cb);
|
||||
|
||||
if (rets) {
|
||||
dev_err(dev->dev,
|
||||
"amthif write failed with status = %d\n", rets);
|
||||
goto out;
|
||||
}
|
||||
mutex_unlock(&dev->device_lock);
|
||||
return length;
|
||||
rets = mei_amthif_write(cl, cb);
|
||||
if (!rets)
|
||||
rets = length;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rets = mei_cl_write(cl, write_cb, false);
|
||||
rets = mei_cl_write(cl, cb, false);
|
||||
out:
|
||||
mutex_unlock(&dev->device_lock);
|
||||
if (rets < 0)
|
||||
mei_io_cb_free(write_cb);
|
||||
return rets;
|
||||
}
|
||||
|
||||
@@ -393,12 +363,22 @@ static int mei_ioctl_connect_client(struct file *file,
|
||||
|
||||
/* find ME client we're trying to connect to */
|
||||
me_cl = mei_me_cl_by_uuid(dev, &data->in_client_uuid);
|
||||
if (!me_cl ||
|
||||
(me_cl->props.fixed_address && !dev->allow_fixed_address)) {
|
||||
if (!me_cl) {
|
||||
dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
|
||||
&data->in_client_uuid);
|
||||
mei_me_cl_put(me_cl);
|
||||
return -ENOTTY;
|
||||
rets = -ENOTTY;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (me_cl->props.fixed_address) {
|
||||
bool forbidden = dev->override_fixed_address ?
|
||||
!dev->allow_fixed_address : !dev->hbm_f_fa_supported;
|
||||
if (forbidden) {
|
||||
dev_dbg(dev->dev, "Connection forbidden to FW Client UUID = %pUl\n",
|
||||
&data->in_client_uuid);
|
||||
rets = -ENOTTY;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "Connect to FW Client ID = %d\n",
|
||||
@@ -454,7 +434,7 @@ end:
|
||||
*
|
||||
* Return: 0 on success , <0 on error
|
||||
*/
|
||||
static int mei_ioctl_client_notify_request(struct file *file, u32 request)
|
||||
static int mei_ioctl_client_notify_request(const struct file *file, u32 request)
|
||||
{
|
||||
struct mei_cl *cl = file->private_data;
|
||||
|
||||
@@ -473,7 +453,7 @@ static int mei_ioctl_client_notify_request(struct file *file, u32 request)
|
||||
*
|
||||
* Return: 0 on success , <0 on error
|
||||
*/
|
||||
static int mei_ioctl_client_notify_get(struct file *file, u32 *notify_get)
|
||||
static int mei_ioctl_client_notify_get(const struct file *file, u32 *notify_get)
|
||||
{
|
||||
struct mei_cl *cl = file->private_data;
|
||||
bool notify_ev;
|
||||
|
@@ -22,4 +22,6 @@
|
||||
|
||||
EXPORT_TRACEPOINT_SYMBOL(mei_reg_read);
|
||||
EXPORT_TRACEPOINT_SYMBOL(mei_reg_write);
|
||||
EXPORT_TRACEPOINT_SYMBOL(mei_pci_cfg_read);
|
||||
EXPORT_TRACEPOINT_SYMBOL(mei_pci_cfg_write);
|
||||
#endif /* __CHECKER__ */
|
||||
|
@@ -60,7 +60,45 @@ TRACE_EVENT(mei_reg_write,
|
||||
__entry->offs = offs;
|
||||
__entry->val = val;
|
||||
),
|
||||
TP_printk("[%s] write %s[%#x] = %#x)",
|
||||
TP_printk("[%s] write %s[%#x] = %#x",
|
||||
__get_str(dev), __entry->reg, __entry->offs, __entry->val)
|
||||
);
|
||||
|
||||
TRACE_EVENT(mei_pci_cfg_read,
|
||||
TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val),
|
||||
TP_ARGS(dev, reg, offs, val),
|
||||
TP_STRUCT__entry(
|
||||
__string(dev, dev_name(dev))
|
||||
__field(const char *, reg)
|
||||
__field(u32, offs)
|
||||
__field(u32, val)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(dev, dev_name(dev))
|
||||
__entry->reg = reg;
|
||||
__entry->offs = offs;
|
||||
__entry->val = val;
|
||||
),
|
||||
TP_printk("[%s] pci cfg read %s:[%#x] = %#x",
|
||||
__get_str(dev), __entry->reg, __entry->offs, __entry->val)
|
||||
);
|
||||
|
||||
TRACE_EVENT(mei_pci_cfg_write,
|
||||
TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val),
|
||||
TP_ARGS(dev, reg, offs, val),
|
||||
TP_STRUCT__entry(
|
||||
__string(dev, dev_name(dev))
|
||||
__field(const char *, reg)
|
||||
__field(u32, offs)
|
||||
__field(u32, val)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(dev, dev_name(dev))
|
||||
__entry->reg = reg;
|
||||
__entry->offs = offs;
|
||||
__entry->val = val;
|
||||
),
|
||||
TP_printk("[%s] pci cfg write %s[%#x] = %#x",
|
||||
__get_str(dev), __entry->reg, __entry->offs, __entry->val)
|
||||
);
|
||||
|
||||
|
@@ -18,7 +18,7 @@
|
||||
#define _MEI_DEV_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/mei.h>
|
||||
#include <linux/mei_cl_bus.h>
|
||||
@@ -26,33 +26,13 @@
|
||||
#include "hw.h"
|
||||
#include "hbm.h"
|
||||
|
||||
/*
|
||||
* watch dog definition
|
||||
*/
|
||||
#define MEI_WD_HDR_SIZE 4
|
||||
#define MEI_WD_STOP_MSG_SIZE MEI_WD_HDR_SIZE
|
||||
#define MEI_WD_START_MSG_SIZE (MEI_WD_HDR_SIZE + 16)
|
||||
|
||||
#define MEI_WD_DEFAULT_TIMEOUT 120 /* seconds */
|
||||
#define MEI_WD_MIN_TIMEOUT 120 /* seconds */
|
||||
#define MEI_WD_MAX_TIMEOUT 65535 /* seconds */
|
||||
|
||||
#define MEI_WD_STOP_TIMEOUT 10 /* msecs */
|
||||
|
||||
#define MEI_WD_STATE_INDEPENDENCE_MSG_SENT (1 << 0)
|
||||
|
||||
#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32))
|
||||
|
||||
|
||||
/*
|
||||
* AMTHI Client UUID
|
||||
*/
|
||||
extern const uuid_le mei_amthif_guid;
|
||||
|
||||
/*
|
||||
* Watchdog Client UUID
|
||||
*/
|
||||
extern const uuid_le mei_wd_guid;
|
||||
#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32))
|
||||
|
||||
/*
|
||||
* Number of Maximum MEI Clients
|
||||
@@ -73,15 +53,6 @@ extern const uuid_le mei_wd_guid;
|
||||
*/
|
||||
#define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 1)
|
||||
|
||||
/*
|
||||
* Internal Clients Number
|
||||
*/
|
||||
#define MEI_HOST_CLIENT_ID_ANY (-1)
|
||||
#define MEI_HBM_HOST_CLIENT_ID 0 /* not used, just for documentation */
|
||||
#define MEI_WD_HOST_CLIENT_ID 1
|
||||
#define MEI_IAMTHIF_HOST_CLIENT_ID 2
|
||||
|
||||
|
||||
/* File state */
|
||||
enum file_state {
|
||||
MEI_FILE_INITIALIZING = 0,
|
||||
@@ -123,12 +94,6 @@ enum mei_file_transaction_states {
|
||||
MEI_READ_COMPLETE
|
||||
};
|
||||
|
||||
enum mei_wd_states {
|
||||
MEI_WD_IDLE,
|
||||
MEI_WD_RUNNING,
|
||||
MEI_WD_STOPPING,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum mei_cb_file_ops - file operation associated with the callback
|
||||
* @MEI_FOP_READ: read
|
||||
@@ -153,7 +118,7 @@ enum mei_cb_file_ops {
|
||||
* Intel MEI message data struct
|
||||
*/
|
||||
struct mei_msg_data {
|
||||
u32 size;
|
||||
size_t size;
|
||||
unsigned char *data;
|
||||
};
|
||||
|
||||
@@ -206,8 +171,7 @@ struct mei_cl;
|
||||
* @fop_type: file operation type
|
||||
* @buf: buffer for data associated with the callback
|
||||
* @buf_idx: last read index
|
||||
* @read_time: last read operation time stamp (iamthif)
|
||||
* @file_object: pointer to file structure
|
||||
* @fp: pointer to file structure
|
||||
* @status: io status of the cb
|
||||
* @internal: communication between driver and FW flag
|
||||
* @completed: the transfer or reception has completed
|
||||
@@ -217,9 +181,8 @@ struct mei_cl_cb {
|
||||
struct mei_cl *cl;
|
||||
enum mei_cb_file_ops fop_type;
|
||||
struct mei_msg_data buf;
|
||||
unsigned long buf_idx;
|
||||
unsigned long read_time;
|
||||
struct file *file_object;
|
||||
size_t buf_idx;
|
||||
const struct file *fp;
|
||||
int status;
|
||||
u32 internal:1;
|
||||
u32 completed:1;
|
||||
@@ -341,12 +304,13 @@ struct mei_hw_ops {
|
||||
|
||||
/* MEI bus API*/
|
||||
void mei_cl_bus_rescan(struct mei_device *bus);
|
||||
void mei_cl_bus_rescan_work(struct work_struct *work);
|
||||
void mei_cl_bus_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_notify_event(struct mei_cl *cl);
|
||||
bool mei_cl_bus_rx_event(struct mei_cl *cl);
|
||||
bool 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);
|
||||
@@ -404,7 +368,6 @@ const char *mei_pg_state_str(enum mei_pg_state state);
|
||||
* @wait_hw_ready : wait queue for receive HW ready message form FW
|
||||
* @wait_pg : wait queue for receive PG message from FW
|
||||
* @wait_hbm_start : wait queue for receive HBM start message from FW
|
||||
* @wait_stop_wd : wait queue for receive WD stop message from FW
|
||||
*
|
||||
* @reset_count : number of consecutive resets
|
||||
* @dev_state : device state
|
||||
@@ -426,6 +389,8 @@ const char *mei_pg_state_str(enum mei_pg_state state);
|
||||
* @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
|
||||
* @hbm_f_fa_supported : hbm feature fixed address client
|
||||
* @hbm_f_ie_supported : hbm feature immediate reply to enum request
|
||||
*
|
||||
* @me_clients_rwsem: rw lock over me_clients list
|
||||
* @me_clients : list of FW clients
|
||||
@@ -434,26 +399,19 @@ const char *mei_pg_state_str(enum mei_pg_state state);
|
||||
* @me_client_index : last FW client index in enumeration
|
||||
*
|
||||
* @allow_fixed_address: allow user space to connect a fixed client
|
||||
*
|
||||
* @wd_cl : watchdog client
|
||||
* @wd_state : watchdog client state
|
||||
* @wd_pending : watchdog command is pending
|
||||
* @wd_timeout : watchdog expiration timeout
|
||||
* @wd_data : watchdog message buffer
|
||||
* @override_fixed_address: force allow fixed address behavior
|
||||
*
|
||||
* @amthif_cmd_list : amthif list for cmd waiting
|
||||
* @amthif_rd_complete_list : amthif list for reading completed cmd data
|
||||
* @iamthif_file_object : file for current amthif operation
|
||||
* @iamthif_fp : file for current amthif operation
|
||||
* @iamthif_cl : amthif host client
|
||||
* @iamthif_current_cb : amthif current operation callback
|
||||
* @iamthif_open_count : number of opened amthif connections
|
||||
* @iamthif_timer : time stamp of current amthif command completion
|
||||
* @iamthif_stall_timer : timer to detect amthif hang
|
||||
* @iamthif_state : amthif processor state
|
||||
* @iamthif_canceled : current amthif command is canceled
|
||||
*
|
||||
* @init_work : work item for the device init
|
||||
* @reset_work : work item for the device reset
|
||||
* @bus_rescan_work : work item for the bus rescan
|
||||
*
|
||||
* @device_list : mei client bus list
|
||||
* @cl_bus_lock : client bus list lock
|
||||
@@ -486,7 +444,6 @@ struct mei_device {
|
||||
wait_queue_head_t wait_hw_ready;
|
||||
wait_queue_head_t wait_pg;
|
||||
wait_queue_head_t wait_hbm_start;
|
||||
wait_queue_head_t wait_stop_wd;
|
||||
|
||||
/*
|
||||
* mei device states
|
||||
@@ -522,6 +479,8 @@ struct mei_device {
|
||||
unsigned int hbm_f_dc_supported:1;
|
||||
unsigned int hbm_f_dot_supported:1;
|
||||
unsigned int hbm_f_ev_supported:1;
|
||||
unsigned int hbm_f_fa_supported:1;
|
||||
unsigned int hbm_f_ie_supported:1;
|
||||
|
||||
struct rw_semaphore me_clients_rwsem;
|
||||
struct list_head me_clients;
|
||||
@@ -530,29 +489,21 @@ struct mei_device {
|
||||
unsigned long me_client_index;
|
||||
|
||||
bool allow_fixed_address;
|
||||
|
||||
struct mei_cl wd_cl;
|
||||
enum mei_wd_states wd_state;
|
||||
bool wd_pending;
|
||||
u16 wd_timeout;
|
||||
unsigned char wd_data[MEI_WD_START_MSG_SIZE];
|
||||
|
||||
bool override_fixed_address;
|
||||
|
||||
/* amthif list for cmd waiting */
|
||||
struct mei_cl_cb amthif_cmd_list;
|
||||
/* driver managed amthif list for reading completed amthif cmd data */
|
||||
struct mei_cl_cb amthif_rd_complete_list;
|
||||
struct file *iamthif_file_object;
|
||||
const struct file *iamthif_fp;
|
||||
struct mei_cl iamthif_cl;
|
||||
struct mei_cl_cb *iamthif_current_cb;
|
||||
long iamthif_open_count;
|
||||
unsigned long iamthif_timer;
|
||||
u32 iamthif_stall_timer;
|
||||
enum iamthif_states iamthif_state;
|
||||
bool iamthif_canceled;
|
||||
|
||||
struct work_struct init_work;
|
||||
struct work_struct reset_work;
|
||||
struct work_struct bus_rescan_work;
|
||||
|
||||
/* List of bus devices */
|
||||
struct list_head device_list;
|
||||
@@ -635,46 +586,17 @@ unsigned int mei_amthif_poll(struct mei_device *dev,
|
||||
|
||||
int mei_amthif_release(struct mei_device *dev, struct file *file);
|
||||
|
||||
struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev,
|
||||
struct file *file);
|
||||
|
||||
int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||
int mei_amthif_run_next_cmd(struct mei_device *dev);
|
||||
int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
|
||||
void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb);
|
||||
void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||
int mei_amthif_irq_read_msg(struct mei_cl *cl,
|
||||
struct mei_msg_hdr *mei_hdr,
|
||||
struct mei_cl_cb *complete_list);
|
||||
int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);
|
||||
|
||||
/*
|
||||
* NFC functions
|
||||
*/
|
||||
int mei_nfc_host_init(struct mei_device *dev, struct mei_me_client *me_cl);
|
||||
void mei_nfc_host_exit(struct mei_device *dev);
|
||||
|
||||
/*
|
||||
* NFC Client UUID
|
||||
*/
|
||||
extern const uuid_le mei_nfc_guid;
|
||||
|
||||
int mei_wd_send(struct mei_device *dev);
|
||||
int mei_wd_stop(struct mei_device *dev);
|
||||
int mei_wd_host_init(struct mei_device *dev, struct mei_me_client *me_cl);
|
||||
/*
|
||||
* mei_watchdog_register - Registering watchdog interface
|
||||
* once we got connection to the WD Client
|
||||
* @dev: mei device
|
||||
*/
|
||||
int mei_watchdog_register(struct mei_device *dev);
|
||||
/*
|
||||
* mei_watchdog_unregister - Unregistering watchdog interface
|
||||
* @dev: mei device
|
||||
*/
|
||||
void mei_watchdog_unregister(struct mei_device *dev);
|
||||
|
||||
/*
|
||||
* Register Access Function
|
||||
*/
|
||||
|
@@ -88,6 +88,9 @@ static const struct pci_device_id mei_me_pci_tbl[] = {
|
||||
{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)},
|
||||
|
||||
{MEI_PCI_DEVICE(MEI_DEV_ID_BXT_M, mei_me_pch8_cfg)},
|
||||
{MEI_PCI_DEVICE(MEI_DEV_ID_APL_I, mei_me_pch8_cfg)},
|
||||
|
||||
/* required last entry */
|
||||
{0, }
|
||||
};
|
||||
@@ -210,7 +213,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
|
||||
err = mei_register(dev, &pdev->dev);
|
||||
if (err)
|
||||
goto release_irq;
|
||||
goto stop;
|
||||
|
||||
pci_set_drvdata(pdev, dev);
|
||||
|
||||
@@ -231,6 +234,8 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
|
||||
return 0;
|
||||
|
||||
stop:
|
||||
mei_stop(dev);
|
||||
release_irq:
|
||||
mei_cancel_work(dev);
|
||||
mei_disable_interrupts(dev);
|
||||
|
@@ -154,7 +154,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
|
||||
err = mei_register(dev, &pdev->dev);
|
||||
if (err)
|
||||
goto release_irq;
|
||||
goto stop;
|
||||
|
||||
pci_set_drvdata(pdev, dev);
|
||||
|
||||
@@ -170,6 +170,8 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
|
||||
return 0;
|
||||
|
||||
stop:
|
||||
mei_stop(dev);
|
||||
release_irq:
|
||||
|
||||
mei_cancel_work(dev);
|
||||
|
@@ -1,391 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Intel Management Engine Interface (Intel MEI) Linux driver
|
||||
* Copyright (c) 2003-2012, 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#include <linux/mei.h>
|
||||
|
||||
#include "mei_dev.h"
|
||||
#include "hbm.h"
|
||||
#include "client.h"
|
||||
|
||||
static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 };
|
||||
static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 };
|
||||
|
||||
/*
|
||||
* AMT Watchdog Device
|
||||
*/
|
||||
#define INTEL_AMT_WATCHDOG_ID "INTCAMT"
|
||||
|
||||
/* UUIDs for AMT F/W clients */
|
||||
const uuid_le mei_wd_guid = UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, 0x89,
|
||||
0x9D, 0xA9, 0x15, 0x14, 0xCB,
|
||||
0x32, 0xAB);
|
||||
|
||||
static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout)
|
||||
{
|
||||
dev_dbg(dev->dev, "wd: set timeout=%d.\n", timeout);
|
||||
memcpy(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE);
|
||||
memcpy(dev->wd_data + MEI_WD_HDR_SIZE, &timeout, sizeof(u16));
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd_host_init - connect to the watchdog client
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @me_cl: me client
|
||||
*
|
||||
* Return: -ENOTTY if wd client cannot be found
|
||||
* -EIO if write has failed
|
||||
* 0 on success
|
||||
*/
|
||||
int mei_wd_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
|
||||
{
|
||||
struct mei_cl *cl = &dev->wd_cl;
|
||||
int ret;
|
||||
|
||||
mei_cl_init(cl, dev);
|
||||
|
||||
dev->wd_timeout = MEI_WD_DEFAULT_TIMEOUT;
|
||||
dev->wd_state = MEI_WD_IDLE;
|
||||
|
||||
ret = mei_cl_link(cl, MEI_WD_HOST_CLIENT_ID);
|
||||
if (ret < 0) {
|
||||
dev_info(dev->dev, "wd: failed link client\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mei_cl_connect(cl, me_cl, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "wd: failed to connect = %d\n", ret);
|
||||
mei_cl_unlink(cl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mei_watchdog_register(dev);
|
||||
if (ret) {
|
||||
mei_cl_disconnect(cl);
|
||||
mei_cl_unlink(cl);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd_send - sends watch dog message to fw.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* Return: 0 if success,
|
||||
* -EIO when message send fails
|
||||
* -EINVAL when invalid message is to be sent
|
||||
* -ENODEV on flow control failure
|
||||
*/
|
||||
int mei_wd_send(struct mei_device *dev)
|
||||
{
|
||||
struct mei_cl *cl = &dev->wd_cl;
|
||||
struct mei_msg_hdr hdr;
|
||||
int ret;
|
||||
|
||||
hdr.host_addr = cl->host_client_id;
|
||||
hdr.me_addr = mei_cl_me_id(cl);
|
||||
hdr.msg_complete = 1;
|
||||
hdr.reserved = 0;
|
||||
hdr.internal = 0;
|
||||
|
||||
if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE))
|
||||
hdr.length = MEI_WD_START_MSG_SIZE;
|
||||
else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_HDR_SIZE))
|
||||
hdr.length = MEI_WD_STOP_MSG_SIZE;
|
||||
else {
|
||||
dev_err(dev->dev, "wd: invalid message is to be sent, aborting\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = mei_write_message(dev, &hdr, dev->wd_data);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "wd: write message failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mei_cl_flow_ctrl_reduce(cl);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "wd: flow_ctrl_reduce failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd_stop - sends watchdog stop message to fw.
|
||||
*
|
||||
* @dev: the device structure
|
||||
*
|
||||
* Return: 0 if success
|
||||
* on error:
|
||||
* -EIO when message send fails
|
||||
* -EINVAL when invalid message is to be sent
|
||||
* -ETIME on message timeout
|
||||
*/
|
||||
int mei_wd_stop(struct mei_device *dev)
|
||||
{
|
||||
struct mei_cl *cl = &dev->wd_cl;
|
||||
int ret;
|
||||
|
||||
if (!mei_cl_is_connected(cl) ||
|
||||
dev->wd_state != MEI_WD_RUNNING)
|
||||
return 0;
|
||||
|
||||
memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_STOP_MSG_SIZE);
|
||||
|
||||
dev->wd_state = MEI_WD_STOPPING;
|
||||
|
||||
ret = mei_cl_flow_ctrl_creds(cl);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (ret && mei_hbuf_acquire(dev)) {
|
||||
ret = mei_wd_send(dev);
|
||||
if (ret)
|
||||
goto err;
|
||||
dev->wd_pending = false;
|
||||
} else {
|
||||
dev->wd_pending = true;
|
||||
}
|
||||
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
ret = wait_event_timeout(dev->wait_stop_wd,
|
||||
dev->wd_state == MEI_WD_IDLE,
|
||||
msecs_to_jiffies(MEI_WD_STOP_TIMEOUT));
|
||||
mutex_lock(&dev->device_lock);
|
||||
if (dev->wd_state != MEI_WD_IDLE) {
|
||||
/* timeout */
|
||||
ret = -ETIME;
|
||||
dev_warn(dev->dev, "wd: stop failed to complete ret=%d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
dev_dbg(dev->dev, "wd: stop completed after %u msec\n",
|
||||
MEI_WD_STOP_TIMEOUT - jiffies_to_msecs(ret));
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd_ops_start - wd start command from the watchdog core.
|
||||
*
|
||||
* @wd_dev: watchdog device struct
|
||||
*
|
||||
* Return: 0 if success, negative errno code for failure
|
||||
*/
|
||||
static int mei_wd_ops_start(struct watchdog_device *wd_dev)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl *cl;
|
||||
int err = -ENODEV;
|
||||
|
||||
dev = watchdog_get_drvdata(wd_dev);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
cl = &dev->wd_cl;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
if (dev->dev_state != MEI_DEV_ENABLED) {
|
||||
dev_dbg(dev->dev, "wd: dev_state != MEI_DEV_ENABLED dev_state = %s\n",
|
||||
mei_dev_state_str(dev->dev_state));
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
if (!mei_cl_is_connected(cl)) {
|
||||
cl_dbg(dev, cl, "MEI Driver is not connected to Watchdog Client\n");
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
mei_wd_set_start_timeout(dev, dev->wd_timeout);
|
||||
|
||||
err = 0;
|
||||
end_unlock:
|
||||
mutex_unlock(&dev->device_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd_ops_stop - wd stop command from the watchdog core.
|
||||
*
|
||||
* @wd_dev: watchdog device struct
|
||||
*
|
||||
* Return: 0 if success, negative errno code for failure
|
||||
*/
|
||||
static int mei_wd_ops_stop(struct watchdog_device *wd_dev)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
|
||||
dev = watchdog_get_drvdata(wd_dev);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
mei_wd_stop(dev);
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd_ops_ping - wd ping command from the watchdog core.
|
||||
*
|
||||
* @wd_dev: watchdog device struct
|
||||
*
|
||||
* Return: 0 if success, negative errno code for failure
|
||||
*/
|
||||
static int mei_wd_ops_ping(struct watchdog_device *wd_dev)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl *cl;
|
||||
int ret;
|
||||
|
||||
dev = watchdog_get_drvdata(wd_dev);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
cl = &dev->wd_cl;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
if (!mei_cl_is_connected(cl)) {
|
||||
cl_err(dev, cl, "wd: not connected.\n");
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
dev->wd_state = MEI_WD_RUNNING;
|
||||
|
||||
ret = mei_cl_flow_ctrl_creds(cl);
|
||||
if (ret < 0)
|
||||
goto end;
|
||||
|
||||
/* Check if we can send the ping to HW*/
|
||||
if (ret && mei_hbuf_acquire(dev)) {
|
||||
dev_dbg(dev->dev, "wd: sending ping\n");
|
||||
|
||||
ret = mei_wd_send(dev);
|
||||
if (ret)
|
||||
goto end;
|
||||
dev->wd_pending = false;
|
||||
} else {
|
||||
dev->wd_pending = true;
|
||||
}
|
||||
|
||||
end:
|
||||
mutex_unlock(&dev->device_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd_ops_set_timeout - wd set timeout command from the watchdog core.
|
||||
*
|
||||
* @wd_dev: watchdog device struct
|
||||
* @timeout: timeout value to set
|
||||
*
|
||||
* Return: 0 if success, negative errno code for failure
|
||||
*/
|
||||
static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev,
|
||||
unsigned int timeout)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
|
||||
dev = watchdog_get_drvdata(wd_dev);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
/* Check Timeout value */
|
||||
if (timeout < MEI_WD_MIN_TIMEOUT || timeout > MEI_WD_MAX_TIMEOUT)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
dev->wd_timeout = timeout;
|
||||
wd_dev->timeout = timeout;
|
||||
mei_wd_set_start_timeout(dev, dev->wd_timeout);
|
||||
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Watchdog Device structs
|
||||
*/
|
||||
static const struct watchdog_ops wd_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = mei_wd_ops_start,
|
||||
.stop = mei_wd_ops_stop,
|
||||
.ping = mei_wd_ops_ping,
|
||||
.set_timeout = mei_wd_ops_set_timeout,
|
||||
};
|
||||
static const struct watchdog_info wd_info = {
|
||||
.identity = INTEL_AMT_WATCHDOG_ID,
|
||||
.options = WDIOF_KEEPALIVEPING |
|
||||
WDIOF_SETTIMEOUT |
|
||||
WDIOF_ALARMONLY,
|
||||
};
|
||||
|
||||
static struct watchdog_device amt_wd_dev = {
|
||||
.info = &wd_info,
|
||||
.ops = &wd_ops,
|
||||
.timeout = MEI_WD_DEFAULT_TIMEOUT,
|
||||
.min_timeout = MEI_WD_MIN_TIMEOUT,
|
||||
.max_timeout = MEI_WD_MAX_TIMEOUT,
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
mutex_lock(&dev->device_lock);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "wd: unable to register watchdog device = %d.\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "wd: successfully register watchdog interface.\n");
|
||||
watchdog_set_drvdata(&amt_wd_dev, dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mei_watchdog_unregister(struct mei_device *dev)
|
||||
{
|
||||
if (watchdog_get_drvdata(&amt_wd_dev) == NULL)
|
||||
return;
|
||||
|
||||
watchdog_set_drvdata(&amt_wd_dev, NULL);
|
||||
watchdog_unregister_device(&amt_wd_dev);
|
||||
}
|
||||
|
@@ -32,12 +32,29 @@ config SCIF_BUS
|
||||
OS and tools for MIC to use with this driver are available from
|
||||
<http://software.intel.com/en-us/mic-developer>.
|
||||
|
||||
comment "VOP Bus Driver"
|
||||
|
||||
config VOP_BUS
|
||||
tristate "VOP Bus Driver"
|
||||
depends on 64BIT && PCI && X86 && X86_DEV_DMA_OPS
|
||||
help
|
||||
This option is selected by any driver which registers a
|
||||
device or driver on the VOP Bus, such as CONFIG_INTEL_MIC_HOST
|
||||
and CONFIG_INTEL_MIC_CARD.
|
||||
|
||||
If you are building a host/card kernel with an Intel MIC device
|
||||
then say M (recommended) or Y, else say N. If unsure say N.
|
||||
|
||||
More information about the Intel MIC family as well as the Linux
|
||||
OS and tools for MIC to use with this driver are available from
|
||||
<http://software.intel.com/en-us/mic-developer>.
|
||||
|
||||
comment "Intel MIC Host Driver"
|
||||
|
||||
config INTEL_MIC_HOST
|
||||
tristate "Intel MIC Host Driver"
|
||||
depends on 64BIT && PCI && X86 && INTEL_MIC_BUS && SCIF_BUS && MIC_COSM
|
||||
select VHOST_RING
|
||||
depends on 64BIT && PCI && X86
|
||||
depends on INTEL_MIC_BUS && SCIF_BUS && MIC_COSM && VOP_BUS
|
||||
help
|
||||
This enables Host Driver support for the Intel Many Integrated
|
||||
Core (MIC) family of PCIe form factor coprocessor devices that
|
||||
@@ -56,7 +73,8 @@ comment "Intel MIC Card Driver"
|
||||
|
||||
config INTEL_MIC_CARD
|
||||
tristate "Intel MIC Card Driver"
|
||||
depends on 64BIT && X86 && INTEL_MIC_BUS && SCIF_BUS && MIC_COSM
|
||||
depends on 64BIT && X86
|
||||
depends on INTEL_MIC_BUS && SCIF_BUS && MIC_COSM && VOP_BUS
|
||||
select VIRTIO
|
||||
help
|
||||
This enables card driver support for the Intel Many Integrated
|
||||
@@ -107,3 +125,23 @@ config MIC_COSM
|
||||
More information about the Intel MIC family as well as the Linux
|
||||
OS and tools for MIC to use with this driver are available from
|
||||
<http://software.intel.com/en-us/mic-developer>.
|
||||
|
||||
comment "VOP Driver"
|
||||
|
||||
config VOP
|
||||
tristate "VOP Driver"
|
||||
depends on 64BIT && PCI && X86 && VOP_BUS
|
||||
select VHOST_RING
|
||||
help
|
||||
This enables VOP (Virtio over PCIe) Driver support for the Intel
|
||||
Many Integrated Core (MIC) family of PCIe form factor coprocessor
|
||||
devices. The VOP driver allows virtio drivers, e.g. net, console
|
||||
and block drivers, on the card connect to user space virtio
|
||||
devices on the host.
|
||||
|
||||
If you are building a host kernel with an Intel MIC device then
|
||||
say M (recommended) or Y, else say N. If unsure say N.
|
||||
|
||||
More information about the Intel MIC family as well as the Linux
|
||||
OS and tools for MIC to use with this driver are available from
|
||||
<http://software.intel.com/en-us/mic-developer>.
|
||||
|
@@ -8,3 +8,4 @@ obj-y += bus/
|
||||
obj-$(CONFIG_SCIF) += scif/
|
||||
obj-$(CONFIG_MIC_COSM) += cosm/
|
||||
obj-$(CONFIG_MIC_COSM) += cosm_client/
|
||||
obj-$(CONFIG_VOP) += vop/
|
||||
|
@@ -5,3 +5,4 @@
|
||||
obj-$(CONFIG_INTEL_MIC_BUS) += mic_bus.o
|
||||
obj-$(CONFIG_SCIF_BUS) += scif_bus.o
|
||||
obj-$(CONFIG_MIC_COSM) += cosm_bus.o
|
||||
obj-$(CONFIG_VOP_BUS) += vop_bus.o
|
||||
|
@@ -30,6 +30,7 @@
|
||||
* @attr_group: Pointer to list of sysfs attribute groups.
|
||||
* @sdev: Device for sysfs entries.
|
||||
* @state: MIC state.
|
||||
* @prev_state: MIC state previous to MIC_RESETTING
|
||||
* @shutdown_status: MIC status reported by card for shutdown/crashes.
|
||||
* @shutdown_status_int: Internal shutdown status maintained by the driver
|
||||
* @cosm_mutex: Mutex for synchronizing access to data structures.
|
||||
@@ -55,6 +56,7 @@ struct cosm_device {
|
||||
const struct attribute_group **attr_group;
|
||||
struct device *sdev;
|
||||
u8 state;
|
||||
u8 prev_state;
|
||||
u8 shutdown_status;
|
||||
u8 shutdown_status_int;
|
||||
struct mutex cosm_mutex;
|
||||
|
203
drivers/misc/mic/bus/vop_bus.c
Normal file
203
drivers/misc/mic/bus/vop_bus.c
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2016 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Intel Virtio Over PCIe (VOP) Bus driver.
|
||||
*/
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include "vop_bus.h"
|
||||
|
||||
static ssize_t device_show(struct device *d,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct vop_device *dev = dev_to_vop(d);
|
||||
|
||||
return sprintf(buf, "0x%04x\n", dev->id.device);
|
||||
}
|
||||
static DEVICE_ATTR_RO(device);
|
||||
|
||||
static ssize_t vendor_show(struct device *d,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct vop_device *dev = dev_to_vop(d);
|
||||
|
||||
return sprintf(buf, "0x%04x\n", dev->id.vendor);
|
||||
}
|
||||
static DEVICE_ATTR_RO(vendor);
|
||||
|
||||
static ssize_t modalias_show(struct device *d,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct vop_device *dev = dev_to_vop(d);
|
||||
|
||||
return sprintf(buf, "vop:d%08Xv%08X\n",
|
||||
dev->id.device, dev->id.vendor);
|
||||
}
|
||||
static DEVICE_ATTR_RO(modalias);
|
||||
|
||||
static struct attribute *vop_dev_attrs[] = {
|
||||
&dev_attr_device.attr,
|
||||
&dev_attr_vendor.attr,
|
||||
&dev_attr_modalias.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(vop_dev);
|
||||
|
||||
static inline int vop_id_match(const struct vop_device *dev,
|
||||
const struct vop_device_id *id)
|
||||
{
|
||||
if (id->device != dev->id.device && id->device != VOP_DEV_ANY_ID)
|
||||
return 0;
|
||||
|
||||
return id->vendor == VOP_DEV_ANY_ID || id->vendor == dev->id.vendor;
|
||||
}
|
||||
|
||||
/*
|
||||
* This looks through all the IDs a driver claims to support. If any of them
|
||||
* match, we return 1 and the kernel will call vop_dev_probe().
|
||||
*/
|
||||
static int vop_dev_match(struct device *dv, struct device_driver *dr)
|
||||
{
|
||||
unsigned int i;
|
||||
struct vop_device *dev = dev_to_vop(dv);
|
||||
const struct vop_device_id *ids;
|
||||
|
||||
ids = drv_to_vop(dr)->id_table;
|
||||
for (i = 0; ids[i].device; i++)
|
||||
if (vop_id_match(dev, &ids[i]))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vop_uevent(struct device *dv, struct kobj_uevent_env *env)
|
||||
{
|
||||
struct vop_device *dev = dev_to_vop(dv);
|
||||
|
||||
return add_uevent_var(env, "MODALIAS=vop:d%08Xv%08X",
|
||||
dev->id.device, dev->id.vendor);
|
||||
}
|
||||
|
||||
static int vop_dev_probe(struct device *d)
|
||||
{
|
||||
struct vop_device *dev = dev_to_vop(d);
|
||||
struct vop_driver *drv = drv_to_vop(dev->dev.driver);
|
||||
|
||||
return drv->probe(dev);
|
||||
}
|
||||
|
||||
static int vop_dev_remove(struct device *d)
|
||||
{
|
||||
struct vop_device *dev = dev_to_vop(d);
|
||||
struct vop_driver *drv = drv_to_vop(dev->dev.driver);
|
||||
|
||||
drv->remove(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bus_type vop_bus = {
|
||||
.name = "vop_bus",
|
||||
.match = vop_dev_match,
|
||||
.dev_groups = vop_dev_groups,
|
||||
.uevent = vop_uevent,
|
||||
.probe = vop_dev_probe,
|
||||
.remove = vop_dev_remove,
|
||||
};
|
||||
|
||||
int vop_register_driver(struct vop_driver *driver)
|
||||
{
|
||||
driver->driver.bus = &vop_bus;
|
||||
return driver_register(&driver->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vop_register_driver);
|
||||
|
||||
void vop_unregister_driver(struct vop_driver *driver)
|
||||
{
|
||||
driver_unregister(&driver->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vop_unregister_driver);
|
||||
|
||||
static void vop_release_dev(struct device *d)
|
||||
{
|
||||
put_device(d);
|
||||
}
|
||||
|
||||
struct vop_device *
|
||||
vop_register_device(struct device *pdev, int id,
|
||||
const struct dma_map_ops *dma_ops,
|
||||
struct vop_hw_ops *hw_ops, u8 dnode, struct mic_mw *aper,
|
||||
struct dma_chan *chan)
|
||||
{
|
||||
int ret;
|
||||
struct vop_device *vdev;
|
||||
|
||||
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
|
||||
if (!vdev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
vdev->dev.parent = pdev;
|
||||
vdev->id.device = id;
|
||||
vdev->id.vendor = VOP_DEV_ANY_ID;
|
||||
vdev->dev.archdata.dma_ops = (struct dma_map_ops *)dma_ops;
|
||||
vdev->dev.dma_mask = &vdev->dev.coherent_dma_mask;
|
||||
dma_set_mask(&vdev->dev, DMA_BIT_MASK(64));
|
||||
vdev->dev.release = vop_release_dev;
|
||||
vdev->hw_ops = hw_ops;
|
||||
vdev->dev.bus = &vop_bus;
|
||||
vdev->dnode = dnode;
|
||||
vdev->aper = aper;
|
||||
vdev->dma_ch = chan;
|
||||
vdev->index = dnode - 1;
|
||||
dev_set_name(&vdev->dev, "vop-dev%u", vdev->index);
|
||||
/*
|
||||
* device_register() causes the bus infrastructure to look for a
|
||||
* matching driver.
|
||||
*/
|
||||
ret = device_register(&vdev->dev);
|
||||
if (ret)
|
||||
goto free_vdev;
|
||||
return vdev;
|
||||
free_vdev:
|
||||
kfree(vdev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vop_register_device);
|
||||
|
||||
void vop_unregister_device(struct vop_device *dev)
|
||||
{
|
||||
device_unregister(&dev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vop_unregister_device);
|
||||
|
||||
static int __init vop_init(void)
|
||||
{
|
||||
return bus_register(&vop_bus);
|
||||
}
|
||||
|
||||
static void __exit vop_exit(void)
|
||||
{
|
||||
bus_unregister(&vop_bus);
|
||||
}
|
||||
|
||||
core_initcall(vop_init);
|
||||
module_exit(vop_exit);
|
||||
|
||||
MODULE_AUTHOR("Intel Corporation");
|
||||
MODULE_DESCRIPTION("Intel(R) VOP Bus driver");
|
||||
MODULE_LICENSE("GPL v2");
|
140
drivers/misc/mic/bus/vop_bus.h
Normal file
140
drivers/misc/mic/bus/vop_bus.h
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2016 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Intel Virtio over PCIe Bus driver.
|
||||
*/
|
||||
#ifndef _VOP_BUS_H_
|
||||
#define _VOP_BUS_H_
|
||||
/*
|
||||
* Everything a vop driver needs to work with any particular vop
|
||||
* implementation.
|
||||
*/
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include "../common/mic_dev.h"
|
||||
|
||||
struct vop_device_id {
|
||||
u32 device;
|
||||
u32 vendor;
|
||||
};
|
||||
|
||||
#define VOP_DEV_TRNSP 1
|
||||
#define VOP_DEV_ANY_ID 0xffffffff
|
||||
/*
|
||||
* Size of the internal buffer used during DMA's as an intermediate buffer
|
||||
* for copy to/from user. Must be an integral number of pages.
|
||||
*/
|
||||
#define VOP_INT_DMA_BUF_SIZE PAGE_ALIGN(64 * 1024ULL)
|
||||
|
||||
/**
|
||||
* vop_device - representation of a device using vop
|
||||
* @hw_ops: the hardware ops supported by this device.
|
||||
* @id: the device type identification (used to match it with a driver).
|
||||
* @dev: underlying device.
|
||||
* @dnode - The destination node which this device will communicate with.
|
||||
* @aper: Aperture memory window
|
||||
* @dma_ch - DMA channel
|
||||
* @index: unique position on the vop bus
|
||||
*/
|
||||
struct vop_device {
|
||||
struct vop_hw_ops *hw_ops;
|
||||
struct vop_device_id id;
|
||||
struct device dev;
|
||||
u8 dnode;
|
||||
struct mic_mw *aper;
|
||||
struct dma_chan *dma_ch;
|
||||
int index;
|
||||
};
|
||||
|
||||
/**
|
||||
* vop_driver - operations for a vop I/O driver
|
||||
* @driver: underlying device driver (populate name and owner).
|
||||
* @id_table: the ids serviced by this driver.
|
||||
* @probe: the function to call when a device is found. Returns 0 or -errno.
|
||||
* @remove: the function to call when a device is removed.
|
||||
*/
|
||||
struct vop_driver {
|
||||
struct device_driver driver;
|
||||
const struct vop_device_id *id_table;
|
||||
int (*probe)(struct vop_device *dev);
|
||||
void (*remove)(struct vop_device *dev);
|
||||
};
|
||||
|
||||
/**
|
||||
* vop_hw_ops - Hardware operations for accessing a VOP device on the VOP bus.
|
||||
*
|
||||
* @next_db: Obtain the next available doorbell.
|
||||
* @request_irq: Request an interrupt on a particular doorbell.
|
||||
* @free_irq: Free an interrupt requested previously.
|
||||
* @ack_interrupt: acknowledge an interrupt in the ISR.
|
||||
* @get_remote_dp: Get access to the virtio device page used by the remote
|
||||
* node to add/remove/configure virtio devices.
|
||||
* @get_dp: Get access to the virtio device page used by the self
|
||||
* node to add/remove/configure virtio devices.
|
||||
* @send_intr: Send an interrupt to the peer node on a specified doorbell.
|
||||
* @ioremap: Map a buffer with the specified DMA address and length.
|
||||
* @iounmap: Unmap a buffer previously mapped.
|
||||
* @dma_filter: The DMA filter function to use for obtaining access to
|
||||
* a DMA channel on the peer node.
|
||||
*/
|
||||
struct vop_hw_ops {
|
||||
int (*next_db)(struct vop_device *vpdev);
|
||||
struct mic_irq *(*request_irq)(struct vop_device *vpdev,
|
||||
irqreturn_t (*func)(int irq, void *data),
|
||||
const char *name, void *data,
|
||||
int intr_src);
|
||||
void (*free_irq)(struct vop_device *vpdev,
|
||||
struct mic_irq *cookie, void *data);
|
||||
void (*ack_interrupt)(struct vop_device *vpdev, int num);
|
||||
void __iomem * (*get_remote_dp)(struct vop_device *vpdev);
|
||||
void * (*get_dp)(struct vop_device *vpdev);
|
||||
void (*send_intr)(struct vop_device *vpdev, int db);
|
||||
void __iomem * (*ioremap)(struct vop_device *vpdev,
|
||||
dma_addr_t pa, size_t len);
|
||||
void (*iounmap)(struct vop_device *vpdev, void __iomem *va);
|
||||
};
|
||||
|
||||
struct vop_device *
|
||||
vop_register_device(struct device *pdev, int id,
|
||||
const struct dma_map_ops *dma_ops,
|
||||
struct vop_hw_ops *hw_ops, u8 dnode, struct mic_mw *aper,
|
||||
struct dma_chan *chan);
|
||||
void vop_unregister_device(struct vop_device *dev);
|
||||
int vop_register_driver(struct vop_driver *drv);
|
||||
void vop_unregister_driver(struct vop_driver *drv);
|
||||
|
||||
/*
|
||||
* module_vop_driver() - Helper macro for drivers that don't do
|
||||
* anything special in module init/exit. This eliminates a lot of
|
||||
* boilerplate. Each module may only use this macro once, and
|
||||
* calling it replaces module_init() and module_exit()
|
||||
*/
|
||||
#define module_vop_driver(__vop_driver) \
|
||||
module_driver(__vop_driver, vop_register_driver, \
|
||||
vop_unregister_driver)
|
||||
|
||||
static inline struct vop_device *dev_to_vop(struct device *dev)
|
||||
{
|
||||
return container_of(dev, struct vop_device, dev);
|
||||
}
|
||||
|
||||
static inline struct vop_driver *drv_to_vop(struct device_driver *drv)
|
||||
{
|
||||
return container_of(drv, struct vop_driver, driver);
|
||||
}
|
||||
#endif /* _VOP_BUS_H */
|
@@ -8,4 +8,3 @@ obj-$(CONFIG_INTEL_MIC_CARD) += mic_card.o
|
||||
mic_card-y += mic_x100.o
|
||||
mic_card-y += mic_device.o
|
||||
mic_card-y += mic_debugfs.o
|
||||
mic_card-y += mic_virtio.o
|
||||
|
@@ -34,7 +34,6 @@
|
||||
#include <linux/mic_common.h>
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
static struct mic_driver *g_drv;
|
||||
|
||||
@@ -250,12 +249,82 @@ static struct scif_hw_ops scif_hw_ops = {
|
||||
.iounmap = ___mic_iounmap,
|
||||
};
|
||||
|
||||
static inline struct mic_driver *vpdev_to_mdrv(struct vop_device *vpdev)
|
||||
{
|
||||
return dev_get_drvdata(vpdev->dev.parent);
|
||||
}
|
||||
|
||||
static struct mic_irq *
|
||||
__mic_request_irq(struct vop_device *vpdev,
|
||||
irqreturn_t (*func)(int irq, void *data),
|
||||
const char *name, void *data, int intr_src)
|
||||
{
|
||||
return mic_request_card_irq(func, NULL, name, data, intr_src);
|
||||
}
|
||||
|
||||
static void __mic_free_irq(struct vop_device *vpdev,
|
||||
struct mic_irq *cookie, void *data)
|
||||
{
|
||||
return mic_free_card_irq(cookie, data);
|
||||
}
|
||||
|
||||
static void __mic_ack_interrupt(struct vop_device *vpdev, int num)
|
||||
{
|
||||
struct mic_driver *mdrv = vpdev_to_mdrv(vpdev);
|
||||
|
||||
mic_ack_interrupt(&mdrv->mdev);
|
||||
}
|
||||
|
||||
static int __mic_next_db(struct vop_device *vpdev)
|
||||
{
|
||||
return mic_next_card_db();
|
||||
}
|
||||
|
||||
static void __iomem *__mic_get_remote_dp(struct vop_device *vpdev)
|
||||
{
|
||||
struct mic_driver *mdrv = vpdev_to_mdrv(vpdev);
|
||||
|
||||
return mdrv->dp;
|
||||
}
|
||||
|
||||
static void __mic_send_intr(struct vop_device *vpdev, int db)
|
||||
{
|
||||
struct mic_driver *mdrv = vpdev_to_mdrv(vpdev);
|
||||
|
||||
mic_send_intr(&mdrv->mdev, db);
|
||||
}
|
||||
|
||||
static void __iomem *__mic_ioremap(struct vop_device *vpdev,
|
||||
dma_addr_t pa, size_t len)
|
||||
{
|
||||
struct mic_driver *mdrv = vpdev_to_mdrv(vpdev);
|
||||
|
||||
return mic_card_map(&mdrv->mdev, pa, len);
|
||||
}
|
||||
|
||||
static void __mic_iounmap(struct vop_device *vpdev, void __iomem *va)
|
||||
{
|
||||
struct mic_driver *mdrv = vpdev_to_mdrv(vpdev);
|
||||
|
||||
mic_card_unmap(&mdrv->mdev, va);
|
||||
}
|
||||
|
||||
static struct vop_hw_ops vop_hw_ops = {
|
||||
.request_irq = __mic_request_irq,
|
||||
.free_irq = __mic_free_irq,
|
||||
.ack_interrupt = __mic_ack_interrupt,
|
||||
.next_db = __mic_next_db,
|
||||
.get_remote_dp = __mic_get_remote_dp,
|
||||
.send_intr = __mic_send_intr,
|
||||
.ioremap = __mic_ioremap,
|
||||
.iounmap = __mic_iounmap,
|
||||
};
|
||||
|
||||
static int mic_request_dma_chans(struct mic_driver *mdrv)
|
||||
{
|
||||
dma_cap_mask_t mask;
|
||||
struct dma_chan *chan;
|
||||
|
||||
request_module("mic_x100_dma");
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_MEMCPY, mask);
|
||||
|
||||
@@ -309,9 +378,13 @@ int __init mic_driver_init(struct mic_driver *mdrv)
|
||||
rc = -ENODEV;
|
||||
goto irq_uninit;
|
||||
}
|
||||
rc = mic_devices_init(mdrv);
|
||||
if (rc)
|
||||
mdrv->vpdev = vop_register_device(mdrv->dev, VOP_DEV_TRNSP,
|
||||
NULL, &vop_hw_ops, 0,
|
||||
NULL, mdrv->dma_ch[0]);
|
||||
if (IS_ERR(mdrv->vpdev)) {
|
||||
rc = PTR_ERR(mdrv->vpdev);
|
||||
goto dma_free;
|
||||
}
|
||||
bootparam = mdrv->dp;
|
||||
node_id = ioread8(&bootparam->node_id);
|
||||
mdrv->scdev = scif_register_device(mdrv->dev, MIC_SCIF_DEV,
|
||||
@@ -321,13 +394,13 @@ int __init mic_driver_init(struct mic_driver *mdrv)
|
||||
mdrv->num_dma_ch, true);
|
||||
if (IS_ERR(mdrv->scdev)) {
|
||||
rc = PTR_ERR(mdrv->scdev);
|
||||
goto device_uninit;
|
||||
goto vop_remove;
|
||||
}
|
||||
mic_create_card_debug_dir(mdrv);
|
||||
done:
|
||||
return rc;
|
||||
device_uninit:
|
||||
mic_devices_uninit(mdrv);
|
||||
vop_remove:
|
||||
vop_unregister_device(mdrv->vpdev);
|
||||
dma_free:
|
||||
mic_free_dma_chans(mdrv);
|
||||
irq_uninit:
|
||||
@@ -348,7 +421,7 @@ void mic_driver_uninit(struct mic_driver *mdrv)
|
||||
{
|
||||
mic_delete_card_debug_dir(mdrv);
|
||||
scif_unregister_device(mdrv->scdev);
|
||||
mic_devices_uninit(mdrv);
|
||||
vop_unregister_device(mdrv->vpdev);
|
||||
mic_free_dma_chans(mdrv);
|
||||
mic_uninit_irq();
|
||||
mic_dp_uninit();
|
||||
|
@@ -32,6 +32,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mic_bus.h>
|
||||
#include "../bus/scif_bus.h"
|
||||
#include "../bus/vop_bus.h"
|
||||
|
||||
/**
|
||||
* struct mic_intr_info - Contains h/w specific interrupt sources info
|
||||
@@ -76,6 +77,7 @@ struct mic_device {
|
||||
* @dma_ch - Array of DMA channels
|
||||
* @num_dma_ch - Number of DMA channels available
|
||||
* @scdev: SCIF device on the SCIF virtual bus.
|
||||
* @vpdev: Virtio over PCIe device on the VOP virtual bus.
|
||||
*/
|
||||
struct mic_driver {
|
||||
char name[20];
|
||||
@@ -90,6 +92,7 @@ struct mic_driver {
|
||||
struct dma_chan *dma_ch[MIC_MAX_DMA_CHAN];
|
||||
int num_dma_ch;
|
||||
struct scif_hw_dev *scdev;
|
||||
struct vop_device *vpdev;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -1,634 +0,0 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2013 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Disclaimer: The codes contained in these modules may be specific to
|
||||
* the Intel Software Development Platform codenamed: Knights Ferry, and
|
||||
* the Intel product codenamed: Knights Corner, and are not backward
|
||||
* compatible with other Intel products. Additionally, Intel will NOT
|
||||
* support the codes or instruction set in future products.
|
||||
*
|
||||
* Adapted from:
|
||||
*
|
||||
* virtio for kvm on s390
|
||||
*
|
||||
* Copyright IBM Corp. 2008
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License (version 2 only)
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* Author(s): Christian Borntraeger <borntraeger@de.ibm.com>
|
||||
*
|
||||
* Intel MIC Card driver.
|
||||
*
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/virtio_config.h>
|
||||
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
#define VIRTIO_SUBCODE_64 0x0D00
|
||||
|
||||
#define MIC_MAX_VRINGS 4
|
||||
struct mic_vdev {
|
||||
struct virtio_device vdev;
|
||||
struct mic_device_desc __iomem *desc;
|
||||
struct mic_device_ctrl __iomem *dc;
|
||||
struct mic_device *mdev;
|
||||
void __iomem *vr[MIC_MAX_VRINGS];
|
||||
int used_size[MIC_MAX_VRINGS];
|
||||
struct completion reset_done;
|
||||
struct mic_irq *virtio_cookie;
|
||||
int c2h_vdev_db;
|
||||
};
|
||||
|
||||
static struct mic_irq *virtio_config_cookie;
|
||||
#define to_micvdev(vd) container_of(vd, struct mic_vdev, vdev)
|
||||
|
||||
/* Helper API to obtain the parent of the virtio device */
|
||||
static inline struct device *mic_dev(struct mic_vdev *mvdev)
|
||||
{
|
||||
return mvdev->vdev.dev.parent;
|
||||
}
|
||||
|
||||
/* This gets the device's feature bits. */
|
||||
static u64 mic_get_features(struct virtio_device *vdev)
|
||||
{
|
||||
unsigned int i, bits;
|
||||
u32 features = 0;
|
||||
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
|
||||
u8 __iomem *in_features = mic_vq_features(desc);
|
||||
int feature_len = ioread8(&desc->feature_len);
|
||||
|
||||
bits = min_t(unsigned, feature_len, sizeof(features)) * 8;
|
||||
for (i = 0; i < bits; i++)
|
||||
if (ioread8(&in_features[i / 8]) & (BIT(i % 8)))
|
||||
features |= BIT(i);
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
static int mic_finalize_features(struct virtio_device *vdev)
|
||||
{
|
||||
unsigned int i, bits;
|
||||
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
|
||||
u8 feature_len = ioread8(&desc->feature_len);
|
||||
/* Second half of bitmap is features we accept. */
|
||||
u8 __iomem *out_features =
|
||||
mic_vq_features(desc) + feature_len;
|
||||
|
||||
/* Give virtio_ring a chance to accept features. */
|
||||
vring_transport_features(vdev);
|
||||
|
||||
/* Make sure we don't have any features > 32 bits! */
|
||||
BUG_ON((u32)vdev->features != vdev->features);
|
||||
|
||||
memset_io(out_features, 0, feature_len);
|
||||
bits = min_t(unsigned, feature_len,
|
||||
sizeof(vdev->features)) * 8;
|
||||
for (i = 0; i < bits; i++) {
|
||||
if (__virtio_test_bit(vdev, i))
|
||||
iowrite8(ioread8(&out_features[i / 8]) | (1 << (i % 8)),
|
||||
&out_features[i / 8]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reading and writing elements in config space
|
||||
*/
|
||||
static void mic_get(struct virtio_device *vdev, unsigned int offset,
|
||||
void *buf, unsigned len)
|
||||
{
|
||||
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
|
||||
|
||||
if (offset + len > ioread8(&desc->config_len))
|
||||
return;
|
||||
memcpy_fromio(buf, mic_vq_configspace(desc) + offset, len);
|
||||
}
|
||||
|
||||
static void mic_set(struct virtio_device *vdev, unsigned int offset,
|
||||
const void *buf, unsigned len)
|
||||
{
|
||||
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
|
||||
|
||||
if (offset + len > ioread8(&desc->config_len))
|
||||
return;
|
||||
memcpy_toio(mic_vq_configspace(desc) + offset, buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* The operations to get and set the status word just access the status
|
||||
* field of the device descriptor. set_status also interrupts the host
|
||||
* to tell about status changes.
|
||||
*/
|
||||
static u8 mic_get_status(struct virtio_device *vdev)
|
||||
{
|
||||
return ioread8(&to_micvdev(vdev)->desc->status);
|
||||
}
|
||||
|
||||
static void mic_set_status(struct virtio_device *vdev, u8 status)
|
||||
{
|
||||
struct mic_vdev *mvdev = to_micvdev(vdev);
|
||||
if (!status)
|
||||
return;
|
||||
iowrite8(status, &mvdev->desc->status);
|
||||
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
|
||||
}
|
||||
|
||||
/* Inform host on a virtio device reset and wait for ack from host */
|
||||
static void mic_reset_inform_host(struct virtio_device *vdev)
|
||||
{
|
||||
struct mic_vdev *mvdev = to_micvdev(vdev);
|
||||
struct mic_device_ctrl __iomem *dc = mvdev->dc;
|
||||
int retry;
|
||||
|
||||
iowrite8(0, &dc->host_ack);
|
||||
iowrite8(1, &dc->vdev_reset);
|
||||
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
|
||||
|
||||
/* Wait till host completes all card accesses and acks the reset */
|
||||
for (retry = 100; retry--;) {
|
||||
if (ioread8(&dc->host_ack))
|
||||
break;
|
||||
msleep(100);
|
||||
};
|
||||
|
||||
dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry);
|
||||
|
||||
/* Reset status to 0 in case we timed out */
|
||||
iowrite8(0, &mvdev->desc->status);
|
||||
}
|
||||
|
||||
static void mic_reset(struct virtio_device *vdev)
|
||||
{
|
||||
struct mic_vdev *mvdev = to_micvdev(vdev);
|
||||
|
||||
dev_dbg(mic_dev(mvdev), "%s: virtio id %d\n",
|
||||
__func__, vdev->id.device);
|
||||
|
||||
mic_reset_inform_host(vdev);
|
||||
complete_all(&mvdev->reset_done);
|
||||
}
|
||||
|
||||
/*
|
||||
* The virtio_ring code calls this API when it wants to notify the Host.
|
||||
*/
|
||||
static bool mic_notify(struct virtqueue *vq)
|
||||
{
|
||||
struct mic_vdev *mvdev = vq->priv;
|
||||
|
||||
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mic_del_vq(struct virtqueue *vq, int n)
|
||||
{
|
||||
struct mic_vdev *mvdev = to_micvdev(vq->vdev);
|
||||
struct vring *vr = (struct vring *)(vq + 1);
|
||||
|
||||
free_pages((unsigned long) vr->used, get_order(mvdev->used_size[n]));
|
||||
vring_del_virtqueue(vq);
|
||||
mic_card_unmap(mvdev->mdev, mvdev->vr[n]);
|
||||
mvdev->vr[n] = NULL;
|
||||
}
|
||||
|
||||
static void mic_del_vqs(struct virtio_device *vdev)
|
||||
{
|
||||
struct mic_vdev *mvdev = to_micvdev(vdev);
|
||||
struct virtqueue *vq, *n;
|
||||
int idx = 0;
|
||||
|
||||
dev_dbg(mic_dev(mvdev), "%s\n", __func__);
|
||||
|
||||
list_for_each_entry_safe(vq, n, &vdev->vqs, list)
|
||||
mic_del_vq(vq, idx++);
|
||||
}
|
||||
|
||||
/*
|
||||
* This routine will assign vring's allocated in host/io memory. Code in
|
||||
* virtio_ring.c however continues to access this io memory as if it were local
|
||||
* memory without io accessors.
|
||||
*/
|
||||
static struct virtqueue *mic_find_vq(struct virtio_device *vdev,
|
||||
unsigned index,
|
||||
void (*callback)(struct virtqueue *vq),
|
||||
const char *name)
|
||||
{
|
||||
struct mic_vdev *mvdev = to_micvdev(vdev);
|
||||
struct mic_vqconfig __iomem *vqconfig;
|
||||
struct mic_vqconfig config;
|
||||
struct virtqueue *vq;
|
||||
void __iomem *va;
|
||||
struct _mic_vring_info __iomem *info;
|
||||
void *used;
|
||||
int vr_size, _vr_size, err, magic;
|
||||
struct vring *vr;
|
||||
u8 type = ioread8(&mvdev->desc->type);
|
||||
|
||||
if (index >= ioread8(&mvdev->desc->num_vq))
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if (!name)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
/* First assign the vring's allocated in host memory */
|
||||
vqconfig = mic_vq_config(mvdev->desc) + index;
|
||||
memcpy_fromio(&config, vqconfig, sizeof(config));
|
||||
_vr_size = vring_size(le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN);
|
||||
vr_size = PAGE_ALIGN(_vr_size + sizeof(struct _mic_vring_info));
|
||||
va = mic_card_map(mvdev->mdev, le64_to_cpu(config.address), vr_size);
|
||||
if (!va)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
mvdev->vr[index] = va;
|
||||
memset_io(va, 0x0, _vr_size);
|
||||
vq = vring_new_virtqueue(index, le16_to_cpu(config.num),
|
||||
MIC_VIRTIO_RING_ALIGN, vdev, false,
|
||||
(void __force *)va, mic_notify, callback,
|
||||
name);
|
||||
if (!vq) {
|
||||
err = -ENOMEM;
|
||||
goto unmap;
|
||||
}
|
||||
info = va + _vr_size;
|
||||
magic = ioread32(&info->magic);
|
||||
|
||||
if (WARN(magic != MIC_MAGIC + type + index, "magic mismatch")) {
|
||||
err = -EIO;
|
||||
goto unmap;
|
||||
}
|
||||
|
||||
/* Allocate and reassign used ring now */
|
||||
mvdev->used_size[index] = PAGE_ALIGN(sizeof(__u16) * 3 +
|
||||
sizeof(struct vring_used_elem) *
|
||||
le16_to_cpu(config.num));
|
||||
used = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
|
||||
get_order(mvdev->used_size[index]));
|
||||
if (!used) {
|
||||
err = -ENOMEM;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, err);
|
||||
goto del_vq;
|
||||
}
|
||||
iowrite64(virt_to_phys(used), &vqconfig->used_address);
|
||||
|
||||
/*
|
||||
* To reassign the used ring here we are directly accessing
|
||||
* struct vring_virtqueue which is a private data structure
|
||||
* in virtio_ring.c. At the minimum, a BUILD_BUG_ON() in
|
||||
* vring_new_virtqueue() would ensure that
|
||||
* (&vq->vring == (struct vring *) (&vq->vq + 1));
|
||||
*/
|
||||
vr = (struct vring *)(vq + 1);
|
||||
vr->used = used;
|
||||
|
||||
vq->priv = mvdev;
|
||||
return vq;
|
||||
del_vq:
|
||||
vring_del_virtqueue(vq);
|
||||
unmap:
|
||||
mic_card_unmap(mvdev->mdev, mvdev->vr[index]);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
static int mic_find_vqs(struct virtio_device *vdev, unsigned nvqs,
|
||||
struct virtqueue *vqs[],
|
||||
vq_callback_t *callbacks[],
|
||||
const char * const names[])
|
||||
{
|
||||
struct mic_vdev *mvdev = to_micvdev(vdev);
|
||||
struct mic_device_ctrl __iomem *dc = mvdev->dc;
|
||||
int i, err, retry;
|
||||
|
||||
/* We must have this many virtqueues. */
|
||||
if (nvqs > ioread8(&mvdev->desc->num_vq))
|
||||
return -ENOENT;
|
||||
|
||||
for (i = 0; i < nvqs; ++i) {
|
||||
dev_dbg(mic_dev(mvdev), "%s: %d: %s\n",
|
||||
__func__, i, names[i]);
|
||||
vqs[i] = mic_find_vq(vdev, i, callbacks[i], names[i]);
|
||||
if (IS_ERR(vqs[i])) {
|
||||
err = PTR_ERR(vqs[i]);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
iowrite8(1, &dc->used_address_updated);
|
||||
/*
|
||||
* Send an interrupt to the host to inform it that used
|
||||
* rings have been re-assigned.
|
||||
*/
|
||||
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
|
||||
for (retry = 100; retry--;) {
|
||||
if (!ioread8(&dc->used_address_updated))
|
||||
break;
|
||||
msleep(100);
|
||||
};
|
||||
|
||||
dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry);
|
||||
if (!retry) {
|
||||
err = -ENODEV;
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
mic_del_vqs(vdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* The config ops structure as defined by virtio config
|
||||
*/
|
||||
static struct virtio_config_ops mic_vq_config_ops = {
|
||||
.get_features = mic_get_features,
|
||||
.finalize_features = mic_finalize_features,
|
||||
.get = mic_get,
|
||||
.set = mic_set,
|
||||
.get_status = mic_get_status,
|
||||
.set_status = mic_set_status,
|
||||
.reset = mic_reset,
|
||||
.find_vqs = mic_find_vqs,
|
||||
.del_vqs = mic_del_vqs,
|
||||
};
|
||||
|
||||
static irqreturn_t
|
||||
mic_virtio_intr_handler(int irq, void *data)
|
||||
{
|
||||
struct mic_vdev *mvdev = data;
|
||||
struct virtqueue *vq;
|
||||
|
||||
mic_ack_interrupt(mvdev->mdev);
|
||||
list_for_each_entry(vq, &mvdev->vdev.vqs, list)
|
||||
vring_interrupt(0, vq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void mic_virtio_release_dev(struct device *_d)
|
||||
{
|
||||
/*
|
||||
* No need for a release method similar to virtio PCI.
|
||||
* Provide an empty one to avoid getting a warning from core.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* adds a new device and register it with virtio
|
||||
* appropriate drivers are loaded by the device model
|
||||
*/
|
||||
static int mic_add_device(struct mic_device_desc __iomem *d,
|
||||
unsigned int offset, struct mic_driver *mdrv)
|
||||
{
|
||||
struct mic_vdev *mvdev;
|
||||
int ret;
|
||||
int virtio_db;
|
||||
u8 type = ioread8(&d->type);
|
||||
|
||||
mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL);
|
||||
if (!mvdev) {
|
||||
dev_err(mdrv->dev, "Cannot allocate mic dev %u type %u\n",
|
||||
offset, type);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mvdev->mdev = &mdrv->mdev;
|
||||
mvdev->vdev.dev.parent = mdrv->dev;
|
||||
mvdev->vdev.dev.release = mic_virtio_release_dev;
|
||||
mvdev->vdev.id.device = type;
|
||||
mvdev->vdev.config = &mic_vq_config_ops;
|
||||
mvdev->desc = d;
|
||||
mvdev->dc = (void __iomem *)d + mic_aligned_desc_size(d);
|
||||
init_completion(&mvdev->reset_done);
|
||||
|
||||
virtio_db = mic_next_card_db();
|
||||
mvdev->virtio_cookie = mic_request_card_irq(mic_virtio_intr_handler,
|
||||
NULL, "virtio intr", mvdev, virtio_db);
|
||||
if (IS_ERR(mvdev->virtio_cookie)) {
|
||||
ret = PTR_ERR(mvdev->virtio_cookie);
|
||||
goto kfree;
|
||||
}
|
||||
iowrite8((u8)virtio_db, &mvdev->dc->h2c_vdev_db);
|
||||
mvdev->c2h_vdev_db = ioread8(&mvdev->dc->c2h_vdev_db);
|
||||
|
||||
ret = register_virtio_device(&mvdev->vdev);
|
||||
if (ret) {
|
||||
dev_err(mic_dev(mvdev),
|
||||
"Failed to register mic device %u type %u\n",
|
||||
offset, type);
|
||||
goto free_irq;
|
||||
}
|
||||
iowrite64((u64)mvdev, &mvdev->dc->vdev);
|
||||
dev_dbg(mic_dev(mvdev), "%s: registered mic device %u type %u mvdev %p\n",
|
||||
__func__, offset, type, mvdev);
|
||||
|
||||
return 0;
|
||||
|
||||
free_irq:
|
||||
mic_free_card_irq(mvdev->virtio_cookie, mvdev);
|
||||
kfree:
|
||||
kfree(mvdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* match for a mic device with a specific desc pointer
|
||||
*/
|
||||
static int mic_match_desc(struct device *dev, void *data)
|
||||
{
|
||||
struct virtio_device *vdev = dev_to_virtio(dev);
|
||||
struct mic_vdev *mvdev = to_micvdev(vdev);
|
||||
|
||||
return mvdev->desc == (void __iomem *)data;
|
||||
}
|
||||
|
||||
static void mic_handle_config_change(struct mic_device_desc __iomem *d,
|
||||
unsigned int offset, struct mic_driver *mdrv)
|
||||
{
|
||||
struct mic_device_ctrl __iomem *dc
|
||||
= (void __iomem *)d + mic_aligned_desc_size(d);
|
||||
struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev);
|
||||
|
||||
if (ioread8(&dc->config_change) != MIC_VIRTIO_PARAM_CONFIG_CHANGED)
|
||||
return;
|
||||
|
||||
dev_dbg(mdrv->dev, "%s %d\n", __func__, __LINE__);
|
||||
virtio_config_changed(&mvdev->vdev);
|
||||
iowrite8(1, &dc->guest_ack);
|
||||
}
|
||||
|
||||
/*
|
||||
* removes a virtio device if a hot remove event has been
|
||||
* requested by the host.
|
||||
*/
|
||||
static int mic_remove_device(struct mic_device_desc __iomem *d,
|
||||
unsigned int offset, struct mic_driver *mdrv)
|
||||
{
|
||||
struct mic_device_ctrl __iomem *dc
|
||||
= (void __iomem *)d + mic_aligned_desc_size(d);
|
||||
struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev);
|
||||
u8 status;
|
||||
int ret = -1;
|
||||
|
||||
if (ioread8(&dc->config_change) == MIC_VIRTIO_PARAM_DEV_REMOVE) {
|
||||
dev_dbg(mdrv->dev,
|
||||
"%s %d config_change %d type %d mvdev %p\n",
|
||||
__func__, __LINE__,
|
||||
ioread8(&dc->config_change), ioread8(&d->type), mvdev);
|
||||
|
||||
status = ioread8(&d->status);
|
||||
reinit_completion(&mvdev->reset_done);
|
||||
unregister_virtio_device(&mvdev->vdev);
|
||||
mic_free_card_irq(mvdev->virtio_cookie, mvdev);
|
||||
if (status & VIRTIO_CONFIG_S_DRIVER_OK)
|
||||
wait_for_completion(&mvdev->reset_done);
|
||||
kfree(mvdev);
|
||||
iowrite8(1, &dc->guest_ack);
|
||||
dev_dbg(mdrv->dev, "%s %d guest_ack %d\n",
|
||||
__func__, __LINE__, ioread8(&dc->guest_ack));
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define REMOVE_DEVICES true
|
||||
|
||||
static void mic_scan_devices(struct mic_driver *mdrv, bool remove)
|
||||
{
|
||||
s8 type;
|
||||
unsigned int i;
|
||||
struct mic_device_desc __iomem *d;
|
||||
struct mic_device_ctrl __iomem *dc;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
for (i = sizeof(struct mic_bootparam); i < MIC_DP_SIZE;
|
||||
i += mic_total_desc_size(d)) {
|
||||
d = mdrv->dp + i;
|
||||
dc = (void __iomem *)d + mic_aligned_desc_size(d);
|
||||
/*
|
||||
* This read barrier is paired with the corresponding write
|
||||
* barrier on the host which is inserted before adding or
|
||||
* removing a virtio device descriptor, by updating the type.
|
||||
*/
|
||||
rmb();
|
||||
type = ioread8(&d->type);
|
||||
|
||||
/* end of list */
|
||||
if (type == 0)
|
||||
break;
|
||||
|
||||
if (type == -1)
|
||||
continue;
|
||||
|
||||
/* device already exists */
|
||||
dev = device_find_child(mdrv->dev, (void __force *)d,
|
||||
mic_match_desc);
|
||||
if (dev) {
|
||||
if (remove)
|
||||
iowrite8(MIC_VIRTIO_PARAM_DEV_REMOVE,
|
||||
&dc->config_change);
|
||||
put_device(dev);
|
||||
mic_handle_config_change(d, i, mdrv);
|
||||
ret = mic_remove_device(d, i, mdrv);
|
||||
if (!ret && !remove)
|
||||
iowrite8(-1, &d->type);
|
||||
if (remove) {
|
||||
iowrite8(0, &dc->config_change);
|
||||
iowrite8(0, &dc->guest_ack);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* new device */
|
||||
dev_dbg(mdrv->dev, "%s %d Adding new virtio device %p\n",
|
||||
__func__, __LINE__, d);
|
||||
if (!remove)
|
||||
mic_add_device(d, i, mdrv);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* mic_hotplug_device tries to find changes in the device page.
|
||||
*/
|
||||
static void mic_hotplug_devices(struct work_struct *work)
|
||||
{
|
||||
struct mic_driver *mdrv = container_of(work,
|
||||
struct mic_driver, hotplug_work);
|
||||
|
||||
mic_scan_devices(mdrv, !REMOVE_DEVICES);
|
||||
}
|
||||
|
||||
/*
|
||||
* Interrupt handler for hot plug/config changes etc.
|
||||
*/
|
||||
static irqreturn_t
|
||||
mic_extint_handler(int irq, void *data)
|
||||
{
|
||||
struct mic_driver *mdrv = (struct mic_driver *)data;
|
||||
|
||||
dev_dbg(mdrv->dev, "%s %d hotplug work\n",
|
||||
__func__, __LINE__);
|
||||
mic_ack_interrupt(&mdrv->mdev);
|
||||
schedule_work(&mdrv->hotplug_work);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init function for virtio
|
||||
*/
|
||||
int mic_devices_init(struct mic_driver *mdrv)
|
||||
{
|
||||
int rc;
|
||||
struct mic_bootparam __iomem *bootparam;
|
||||
int config_db;
|
||||
|
||||
INIT_WORK(&mdrv->hotplug_work, mic_hotplug_devices);
|
||||
mic_scan_devices(mdrv, !REMOVE_DEVICES);
|
||||
|
||||
config_db = mic_next_card_db();
|
||||
virtio_config_cookie = mic_request_card_irq(mic_extint_handler, NULL,
|
||||
"virtio_config_intr", mdrv,
|
||||
config_db);
|
||||
if (IS_ERR(virtio_config_cookie)) {
|
||||
rc = PTR_ERR(virtio_config_cookie);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bootparam = mdrv->dp;
|
||||
iowrite8(config_db, &bootparam->h2c_config_db);
|
||||
return 0;
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Uninit function for virtio
|
||||
*/
|
||||
void mic_devices_uninit(struct mic_driver *mdrv)
|
||||
{
|
||||
struct mic_bootparam __iomem *bootparam = mdrv->dp;
|
||||
iowrite8(-1, &bootparam->h2c_config_db);
|
||||
mic_free_card_irq(virtio_config_cookie, mdrv);
|
||||
flush_work(&mdrv->hotplug_work);
|
||||
mic_scan_devices(mdrv, REMOVE_DEVICES);
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2013 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Disclaimer: The codes contained in these modules may be specific to
|
||||
* the Intel Software Development Platform codenamed: Knights Ferry, and
|
||||
* the Intel product codenamed: Knights Corner, and are not backward
|
||||
* compatible with other Intel products. Additionally, Intel will NOT
|
||||
* support the codes or instruction set in future products.
|
||||
*
|
||||
* Intel MIC Card driver.
|
||||
*
|
||||
*/
|
||||
#ifndef __MIC_CARD_VIRTIO_H
|
||||
#define __MIC_CARD_VIRTIO_H
|
||||
|
||||
#include <linux/mic_common.h>
|
||||
#include "mic_device.h"
|
||||
|
||||
/*
|
||||
* 64 bit I/O access
|
||||
*/
|
||||
#ifndef ioread64
|
||||
#define ioread64 readq
|
||||
#endif
|
||||
#ifndef iowrite64
|
||||
#define iowrite64 writeq
|
||||
#endif
|
||||
|
||||
static inline unsigned mic_desc_size(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return sizeof(*desc)
|
||||
+ ioread8(&desc->num_vq) * sizeof(struct mic_vqconfig)
|
||||
+ ioread8(&desc->feature_len) * 2
|
||||
+ ioread8(&desc->config_len);
|
||||
}
|
||||
|
||||
static inline struct mic_vqconfig __iomem *
|
||||
mic_vq_config(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return (struct mic_vqconfig __iomem *)(desc + 1);
|
||||
}
|
||||
|
||||
static inline __u8 __iomem *
|
||||
mic_vq_features(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return (__u8 __iomem *)(mic_vq_config(desc) + ioread8(&desc->num_vq));
|
||||
}
|
||||
|
||||
static inline __u8 __iomem *
|
||||
mic_vq_configspace(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return mic_vq_features(desc) + ioread8(&desc->feature_len) * 2;
|
||||
}
|
||||
static inline unsigned mic_total_desc_size(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return mic_aligned_desc_size(desc) + sizeof(struct mic_device_ctrl);
|
||||
}
|
||||
|
||||
int mic_devices_init(struct mic_driver *mdrv);
|
||||
void mic_devices_uninit(struct mic_driver *mdrv);
|
||||
|
||||
#endif
|
@@ -326,6 +326,7 @@ static int __init mic_init(void)
|
||||
goto done;
|
||||
}
|
||||
|
||||
request_module("mic_x100_dma");
|
||||
mic_init_card_debugfs();
|
||||
ret = platform_device_register(&mic_platform_dev);
|
||||
if (ret) {
|
||||
|
@@ -153,8 +153,10 @@ void cosm_stop(struct cosm_device *cdev, bool force)
|
||||
* stop(..) calls device_unregister and will crash the system if
|
||||
* called multiple times.
|
||||
*/
|
||||
bool call_hw_ops = cdev->state != MIC_RESET_FAILED &&
|
||||
cdev->state != MIC_READY;
|
||||
u8 state = cdev->state == MIC_RESETTING ?
|
||||
cdev->prev_state : cdev->state;
|
||||
bool call_hw_ops = state != MIC_RESET_FAILED &&
|
||||
state != MIC_READY;
|
||||
|
||||
if (cdev->state != MIC_RESETTING)
|
||||
cosm_set_state(cdev, MIC_RESETTING);
|
||||
@@ -195,8 +197,11 @@ int cosm_reset(struct cosm_device *cdev)
|
||||
|
||||
mutex_lock(&cdev->cosm_mutex);
|
||||
if (cdev->state != MIC_READY) {
|
||||
cosm_set_state(cdev, MIC_RESETTING);
|
||||
schedule_work(&cdev->reset_trigger_work);
|
||||
if (cdev->state != MIC_RESETTING) {
|
||||
cdev->prev_state = cdev->state;
|
||||
cosm_set_state(cdev, MIC_RESETTING);
|
||||
schedule_work(&cdev->reset_trigger_work);
|
||||
}
|
||||
} else {
|
||||
dev_err(&cdev->dev, "%s %d MIC is READY\n", __func__, __LINE__);
|
||||
rc = -EINVAL;
|
||||
|
@@ -9,5 +9,3 @@ mic_host-objs += mic_smpt.o
|
||||
mic_host-objs += mic_intr.o
|
||||
mic_host-objs += mic_boot.o
|
||||
mic_host-objs += mic_debugfs.o
|
||||
mic_host-objs += mic_fops.o
|
||||
mic_host-objs += mic_virtio.o
|
||||
|
@@ -25,10 +25,117 @@
|
||||
#include <linux/mic_common.h>
|
||||
#include <linux/mic_bus.h>
|
||||
#include "../bus/scif_bus.h"
|
||||
#include "../bus/vop_bus.h"
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_smpt.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
static inline struct mic_device *vpdev_to_mdev(struct device *dev)
|
||||
{
|
||||
return dev_get_drvdata(dev->parent);
|
||||
}
|
||||
|
||||
static dma_addr_t
|
||||
_mic_dma_map_page(struct device *dev, struct page *page,
|
||||
unsigned long offset, size_t size,
|
||||
enum dma_data_direction dir, struct dma_attrs *attrs)
|
||||
{
|
||||
void *va = phys_to_virt(page_to_phys(page)) + offset;
|
||||
struct mic_device *mdev = vpdev_to_mdev(dev);
|
||||
|
||||
return mic_map_single(mdev, va, size);
|
||||
}
|
||||
|
||||
static void _mic_dma_unmap_page(struct device *dev, dma_addr_t dma_addr,
|
||||
size_t size, enum dma_data_direction dir,
|
||||
struct dma_attrs *attrs)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(dev);
|
||||
|
||||
mic_unmap_single(mdev, dma_addr, size);
|
||||
}
|
||||
|
||||
static const struct dma_map_ops _mic_dma_ops = {
|
||||
.map_page = _mic_dma_map_page,
|
||||
.unmap_page = _mic_dma_unmap_page,
|
||||
};
|
||||
|
||||
static struct mic_irq *
|
||||
__mic_request_irq(struct vop_device *vpdev,
|
||||
irqreturn_t (*func)(int irq, void *data),
|
||||
const char *name, void *data, int intr_src)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev);
|
||||
|
||||
return mic_request_threaded_irq(mdev, func, NULL, name, data,
|
||||
intr_src, MIC_INTR_DB);
|
||||
}
|
||||
|
||||
static void __mic_free_irq(struct vop_device *vpdev,
|
||||
struct mic_irq *cookie, void *data)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev);
|
||||
|
||||
return mic_free_irq(mdev, cookie, data);
|
||||
}
|
||||
|
||||
static void __mic_ack_interrupt(struct vop_device *vpdev, int num)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev);
|
||||
|
||||
mdev->ops->intr_workarounds(mdev);
|
||||
}
|
||||
|
||||
static int __mic_next_db(struct vop_device *vpdev)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev);
|
||||
|
||||
return mic_next_db(mdev);
|
||||
}
|
||||
|
||||
static void *__mic_get_dp(struct vop_device *vpdev)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev);
|
||||
|
||||
return mdev->dp;
|
||||
}
|
||||
|
||||
static void __iomem *__mic_get_remote_dp(struct vop_device *vpdev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void __mic_send_intr(struct vop_device *vpdev, int db)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev);
|
||||
|
||||
mdev->ops->send_intr(mdev, db);
|
||||
}
|
||||
|
||||
static void __iomem *__mic_ioremap(struct vop_device *vpdev,
|
||||
dma_addr_t pa, size_t len)
|
||||
{
|
||||
struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev);
|
||||
|
||||
return mdev->aper.va + pa;
|
||||
}
|
||||
|
||||
static void __mic_iounmap(struct vop_device *vpdev, void __iomem *va)
|
||||
{
|
||||
/* nothing to do */
|
||||
}
|
||||
|
||||
static struct vop_hw_ops vop_hw_ops = {
|
||||
.request_irq = __mic_request_irq,
|
||||
.free_irq = __mic_free_irq,
|
||||
.ack_interrupt = __mic_ack_interrupt,
|
||||
.next_db = __mic_next_db,
|
||||
.get_dp = __mic_get_dp,
|
||||
.get_remote_dp = __mic_get_remote_dp,
|
||||
.send_intr = __mic_send_intr,
|
||||
.ioremap = __mic_ioremap,
|
||||
.iounmap = __mic_iounmap,
|
||||
};
|
||||
|
||||
static inline struct mic_device *scdev_to_mdev(struct scif_hw_dev *scdev)
|
||||
{
|
||||
@@ -315,7 +422,6 @@ static int mic_request_dma_chans(struct mic_device *mdev)
|
||||
dma_cap_mask_t mask;
|
||||
struct dma_chan *chan;
|
||||
|
||||
request_module("mic_x100_dma");
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_MEMCPY, mask);
|
||||
|
||||
@@ -387,9 +493,18 @@ static int _mic_start(struct cosm_device *cdev, int id)
|
||||
goto dma_free;
|
||||
}
|
||||
|
||||
mdev->vpdev = vop_register_device(&mdev->pdev->dev,
|
||||
VOP_DEV_TRNSP, &_mic_dma_ops,
|
||||
&vop_hw_ops, id + 1, &mdev->aper,
|
||||
mdev->dma_ch[0]);
|
||||
if (IS_ERR(mdev->vpdev)) {
|
||||
rc = PTR_ERR(mdev->vpdev);
|
||||
goto scif_remove;
|
||||
}
|
||||
|
||||
rc = mdev->ops->load_mic_fw(mdev, NULL);
|
||||
if (rc)
|
||||
goto scif_remove;
|
||||
goto vop_remove;
|
||||
mic_smpt_restore(mdev);
|
||||
mic_intr_restore(mdev);
|
||||
mdev->intr_ops->enable_interrupts(mdev);
|
||||
@@ -397,6 +512,8 @@ static int _mic_start(struct cosm_device *cdev, int id)
|
||||
mdev->ops->write_spad(mdev, MIC_DPHI_SPAD, mdev->dp_dma_addr >> 32);
|
||||
mdev->ops->send_firmware_intr(mdev);
|
||||
goto unlock_ret;
|
||||
vop_remove:
|
||||
vop_unregister_device(mdev->vpdev);
|
||||
scif_remove:
|
||||
scif_unregister_device(mdev->scdev);
|
||||
dma_free:
|
||||
@@ -423,7 +540,7 @@ static void _mic_stop(struct cosm_device *cdev, bool force)
|
||||
* will be the first to be registered and the last to be
|
||||
* unregistered.
|
||||
*/
|
||||
mic_virtio_reset_devices(mdev);
|
||||
vop_unregister_device(mdev->vpdev);
|
||||
scif_unregister_device(mdev->scdev);
|
||||
mic_free_dma_chans(mdev);
|
||||
mbus_unregister_device(mdev->dma_mbdev);
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_smpt.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
/* Debugfs parent dir */
|
||||
static struct dentry *mic_dbg;
|
||||
@@ -100,190 +99,6 @@ static const struct file_operations post_code_ops = {
|
||||
.release = mic_post_code_debug_release
|
||||
};
|
||||
|
||||
static int mic_dp_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
struct mic_device_desc *d;
|
||||
struct mic_device_ctrl *dc;
|
||||
struct mic_vqconfig *vqconfig;
|
||||
__u32 *features;
|
||||
__u8 *config;
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
int i, j;
|
||||
|
||||
seq_printf(s, "Bootparam: magic 0x%x\n",
|
||||
bootparam->magic);
|
||||
seq_printf(s, "Bootparam: h2c_config_db %d\n",
|
||||
bootparam->h2c_config_db);
|
||||
seq_printf(s, "Bootparam: node_id %d\n",
|
||||
bootparam->node_id);
|
||||
seq_printf(s, "Bootparam: c2h_scif_db %d\n",
|
||||
bootparam->c2h_scif_db);
|
||||
seq_printf(s, "Bootparam: h2c_scif_db %d\n",
|
||||
bootparam->h2c_scif_db);
|
||||
seq_printf(s, "Bootparam: scif_host_dma_addr 0x%llx\n",
|
||||
bootparam->scif_host_dma_addr);
|
||||
seq_printf(s, "Bootparam: scif_card_dma_addr 0x%llx\n",
|
||||
bootparam->scif_card_dma_addr);
|
||||
|
||||
|
||||
for (i = sizeof(*bootparam); i < MIC_DP_SIZE;
|
||||
i += mic_total_desc_size(d)) {
|
||||
d = mdev->dp + i;
|
||||
dc = (void *)d + mic_aligned_desc_size(d);
|
||||
|
||||
/* end of list */
|
||||
if (d->type == 0)
|
||||
break;
|
||||
|
||||
if (d->type == -1)
|
||||
continue;
|
||||
|
||||
seq_printf(s, "Type %d ", d->type);
|
||||
seq_printf(s, "Num VQ %d ", d->num_vq);
|
||||
seq_printf(s, "Feature Len %d\n", d->feature_len);
|
||||
seq_printf(s, "Config Len %d ", d->config_len);
|
||||
seq_printf(s, "Shutdown Status %d\n", d->status);
|
||||
|
||||
for (j = 0; j < d->num_vq; j++) {
|
||||
vqconfig = mic_vq_config(d) + j;
|
||||
seq_printf(s, "vqconfig[%d]: ", j);
|
||||
seq_printf(s, "address 0x%llx ", vqconfig->address);
|
||||
seq_printf(s, "num %d ", vqconfig->num);
|
||||
seq_printf(s, "used address 0x%llx\n",
|
||||
vqconfig->used_address);
|
||||
}
|
||||
|
||||
features = (__u32 *)mic_vq_features(d);
|
||||
seq_printf(s, "Features: Host 0x%x ", features[0]);
|
||||
seq_printf(s, "Guest 0x%x\n", features[1]);
|
||||
|
||||
config = mic_vq_configspace(d);
|
||||
for (j = 0; j < d->config_len; j++)
|
||||
seq_printf(s, "config[%d]=%d\n", j, config[j]);
|
||||
|
||||
seq_puts(s, "Device control:\n");
|
||||
seq_printf(s, "Config Change %d ", dc->config_change);
|
||||
seq_printf(s, "Vdev reset %d\n", dc->vdev_reset);
|
||||
seq_printf(s, "Guest Ack %d ", dc->guest_ack);
|
||||
seq_printf(s, "Host ack %d\n", dc->host_ack);
|
||||
seq_printf(s, "Used address updated %d ",
|
||||
dc->used_address_updated);
|
||||
seq_printf(s, "Vdev 0x%llx\n", dc->vdev);
|
||||
seq_printf(s, "c2h doorbell %d ", dc->c2h_vdev_db);
|
||||
seq_printf(s, "h2c doorbell %d\n", dc->h2c_vdev_db);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_dp_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_dp_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_dp_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations dp_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_dp_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_dp_debug_release
|
||||
};
|
||||
|
||||
static int mic_vdev_info_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
struct list_head *pos, *tmp;
|
||||
struct mic_vdev *mvdev;
|
||||
int i, j;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
list_for_each_safe(pos, tmp, &mdev->vdev_list) {
|
||||
mvdev = list_entry(pos, struct mic_vdev, list);
|
||||
seq_printf(s, "VDEV type %d state %s in %ld out %ld\n",
|
||||
mvdev->virtio_id,
|
||||
mic_vdevup(mvdev) ? "UP" : "DOWN",
|
||||
mvdev->in_bytes,
|
||||
mvdev->out_bytes);
|
||||
for (i = 0; i < MIC_MAX_VRINGS; i++) {
|
||||
struct vring_desc *desc;
|
||||
struct vring_avail *avail;
|
||||
struct vring_used *used;
|
||||
struct mic_vringh *mvr = &mvdev->mvr[i];
|
||||
struct vringh *vrh = &mvr->vrh;
|
||||
int num = vrh->vring.num;
|
||||
if (!num)
|
||||
continue;
|
||||
desc = vrh->vring.desc;
|
||||
seq_printf(s, "vring i %d avail_idx %d",
|
||||
i, mvr->vring.info->avail_idx & (num - 1));
|
||||
seq_printf(s, " vring i %d avail_idx %d\n",
|
||||
i, mvr->vring.info->avail_idx);
|
||||
seq_printf(s, "vrh i %d weak_barriers %d",
|
||||
i, vrh->weak_barriers);
|
||||
seq_printf(s, " last_avail_idx %d last_used_idx %d",
|
||||
vrh->last_avail_idx, vrh->last_used_idx);
|
||||
seq_printf(s, " completed %d\n", vrh->completed);
|
||||
for (j = 0; j < num; j++) {
|
||||
seq_printf(s, "desc[%d] addr 0x%llx len %d",
|
||||
j, desc->addr, desc->len);
|
||||
seq_printf(s, " flags 0x%x next %d\n",
|
||||
desc->flags, desc->next);
|
||||
desc++;
|
||||
}
|
||||
avail = vrh->vring.avail;
|
||||
seq_printf(s, "avail flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, avail->flags),
|
||||
vringh16_to_cpu(vrh, avail->idx) & (num - 1));
|
||||
seq_printf(s, "avail flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, avail->flags),
|
||||
vringh16_to_cpu(vrh, avail->idx));
|
||||
for (j = 0; j < num; j++)
|
||||
seq_printf(s, "avail ring[%d] %d\n",
|
||||
j, avail->ring[j]);
|
||||
used = vrh->vring.used;
|
||||
seq_printf(s, "used flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, used->flags),
|
||||
vringh16_to_cpu(vrh, used->idx) & (num - 1));
|
||||
seq_printf(s, "used flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, used->flags),
|
||||
vringh16_to_cpu(vrh, used->idx));
|
||||
for (j = 0; j < num; j++)
|
||||
seq_printf(s, "used ring[%d] id %d len %d\n",
|
||||
j, vringh32_to_cpu(vrh,
|
||||
used->ring[j].id),
|
||||
vringh32_to_cpu(vrh,
|
||||
used->ring[j].len));
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_vdev_info_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_vdev_info_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_vdev_info_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations vdev_info_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_vdev_info_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_vdev_info_debug_release
|
||||
};
|
||||
|
||||
static int mic_msi_irq_info_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
@@ -367,11 +182,6 @@ void mic_create_debug_dir(struct mic_device *mdev)
|
||||
debugfs_create_file("post_code", 0444, mdev->dbg_dir, mdev,
|
||||
&post_code_ops);
|
||||
|
||||
debugfs_create_file("dp", 0444, mdev->dbg_dir, mdev, &dp_ops);
|
||||
|
||||
debugfs_create_file("vdev_info", 0444, mdev->dbg_dir, mdev,
|
||||
&vdev_info_ops);
|
||||
|
||||
debugfs_create_file("msi_irq_info", 0444, mdev->dbg_dir, mdev,
|
||||
&msi_irq_info_ops);
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/mic_bus.h>
|
||||
#include "../bus/scif_bus.h"
|
||||
#include "../bus/vop_bus.h"
|
||||
#include "../bus/cosm_bus.h"
|
||||
#include "mic_intr.h"
|
||||
|
||||
@@ -64,13 +65,11 @@ extern struct cosm_hw_ops cosm_hw_ops;
|
||||
* @bootaddr: MIC boot address.
|
||||
* @dp: virtio device page
|
||||
* @dp_dma_addr: virtio device page DMA address.
|
||||
* @name: name for the misc char device
|
||||
* @miscdev: registered misc char device
|
||||
* @vdev_list: list of virtio devices.
|
||||
* @dma_mbdev: MIC BUS DMA device.
|
||||
* @dma_ch - Array of DMA channels
|
||||
* @num_dma_ch - Number of DMA channels available
|
||||
* @scdev: SCIF device on the SCIF virtual bus.
|
||||
* @vpdev: Virtio over PCIe device on the VOP virtual bus.
|
||||
* @cosm_dev: COSM device
|
||||
*/
|
||||
struct mic_device {
|
||||
@@ -91,13 +90,11 @@ struct mic_device {
|
||||
u32 bootaddr;
|
||||
void *dp;
|
||||
dma_addr_t dp_dma_addr;
|
||||
char name[16];
|
||||
struct miscdevice miscdev;
|
||||
struct list_head vdev_list;
|
||||
struct mbus_device *dma_mbdev;
|
||||
struct dma_chan *dma_ch[MIC_MAX_DMA_CHAN];
|
||||
int num_dma_ch;
|
||||
struct scif_hw_dev *scdev;
|
||||
struct vop_device *vpdev;
|
||||
struct cosm_device *cosm_dev;
|
||||
};
|
||||
|
||||
|
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2013 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Intel MIC Host driver.
|
||||
*
|
||||
*/
|
||||
#include <linux/poll.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include <linux/mic_common.h>
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_fops.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
int mic_open(struct inode *inode, struct file *f)
|
||||
{
|
||||
struct mic_vdev *mvdev;
|
||||
struct mic_device *mdev = container_of(f->private_data,
|
||||
struct mic_device, miscdev);
|
||||
|
||||
mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL);
|
||||
if (!mvdev)
|
||||
return -ENOMEM;
|
||||
|
||||
init_waitqueue_head(&mvdev->waitq);
|
||||
INIT_LIST_HEAD(&mvdev->list);
|
||||
mvdev->mdev = mdev;
|
||||
mvdev->virtio_id = -1;
|
||||
|
||||
f->private_data = mvdev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mic_release(struct inode *inode, struct file *f)
|
||||
{
|
||||
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
||||
|
||||
if (-1 != mvdev->virtio_id)
|
||||
mic_virtio_del_device(mvdev);
|
||||
f->private_data = NULL;
|
||||
kfree(mvdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
long mic_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
||||
void __user *argp = (void __user *)arg;
|
||||
int ret;
|
||||
|
||||
switch (cmd) {
|
||||
case MIC_VIRTIO_ADD_DEVICE:
|
||||
{
|
||||
ret = mic_virtio_add_device(mvdev, argp);
|
||||
if (ret < 0) {
|
||||
dev_err(mic_dev(mvdev),
|
||||
"%s %d errno ret %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MIC_VIRTIO_COPY_DESC:
|
||||
{
|
||||
struct mic_copy_desc copy;
|
||||
|
||||
ret = mic_vdev_inited(mvdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (copy_from_user(©, argp, sizeof(copy)))
|
||||
return -EFAULT;
|
||||
|
||||
dev_dbg(mic_dev(mvdev),
|
||||
"%s %d === iovcnt 0x%x vr_idx 0x%x update_used %d\n",
|
||||
__func__, __LINE__, copy.iovcnt, copy.vr_idx,
|
||||
copy.update_used);
|
||||
|
||||
ret = mic_virtio_copy_desc(mvdev, ©);
|
||||
if (ret < 0) {
|
||||
dev_err(mic_dev(mvdev),
|
||||
"%s %d errno ret %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
return ret;
|
||||
}
|
||||
if (copy_to_user(
|
||||
&((struct mic_copy_desc __user *)argp)->out_len,
|
||||
©.out_len, sizeof(copy.out_len))) {
|
||||
dev_err(mic_dev(mvdev), "%s %d errno ret %d\n",
|
||||
__func__, __LINE__, -EFAULT);
|
||||
return -EFAULT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MIC_VIRTIO_CONFIG_CHANGE:
|
||||
{
|
||||
ret = mic_vdev_inited(mvdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mic_virtio_config_change(mvdev, argp);
|
||||
if (ret < 0) {
|
||||
dev_err(mic_dev(mvdev),
|
||||
"%s %d errno ret %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We return POLLIN | POLLOUT from poll when new buffers are enqueued, and
|
||||
* not when previously enqueued buffers may be available. This means that
|
||||
* in the card->host (TX) path, when userspace is unblocked by poll it
|
||||
* must drain all available descriptors or it can stall.
|
||||
*/
|
||||
unsigned int mic_poll(struct file *f, poll_table *wait)
|
||||
{
|
||||
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
||||
int mask = 0;
|
||||
|
||||
poll_wait(f, &mvdev->waitq, wait);
|
||||
|
||||
if (mic_vdev_inited(mvdev)) {
|
||||
mask = POLLERR;
|
||||
} else if (mvdev->poll_wake) {
|
||||
mvdev->poll_wake = 0;
|
||||
mask = POLLIN | POLLOUT;
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static inline int
|
||||
mic_query_offset(struct mic_vdev *mvdev, unsigned long offset,
|
||||
unsigned long *size, unsigned long *pa)
|
||||
{
|
||||
struct mic_device *mdev = mvdev->mdev;
|
||||
unsigned long start = MIC_DP_SIZE;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* MMAP interface is as follows:
|
||||
* offset region
|
||||
* 0x0 virtio device_page
|
||||
* 0x1000 first vring
|
||||
* 0x1000 + size of 1st vring second vring
|
||||
* ....
|
||||
*/
|
||||
if (!offset) {
|
||||
*pa = virt_to_phys(mdev->dp);
|
||||
*size = MIC_DP_SIZE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++) {
|
||||
struct mic_vringh *mvr = &mvdev->mvr[i];
|
||||
if (offset == start) {
|
||||
*pa = virt_to_phys(mvr->vring.va);
|
||||
*size = mvr->vring.len;
|
||||
return 0;
|
||||
}
|
||||
start += mvr->vring.len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Maps the device page and virtio rings to user space for readonly access.
|
||||
*/
|
||||
int
|
||||
mic_mmap(struct file *f, struct vm_area_struct *vma)
|
||||
{
|
||||
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
||||
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
|
||||
unsigned long pa, size = vma->vm_end - vma->vm_start, size_rem = size;
|
||||
int i, err;
|
||||
|
||||
err = mic_vdev_inited(mvdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (vma->vm_flags & VM_WRITE)
|
||||
return -EACCES;
|
||||
|
||||
while (size_rem) {
|
||||
i = mic_query_offset(mvdev, offset, &size, &pa);
|
||||
if (i < 0)
|
||||
return -EINVAL;
|
||||
err = remap_pfn_range(vma, vma->vm_start + offset,
|
||||
pa >> PAGE_SHIFT, size, vma->vm_page_prot);
|
||||
if (err)
|
||||
return err;
|
||||
dev_dbg(mic_dev(mvdev),
|
||||
"%s %d type %d size 0x%lx off 0x%lx pa 0x%lx vma 0x%lx\n",
|
||||
__func__, __LINE__, mvdev->virtio_id, size, offset,
|
||||
pa, vma->vm_start + offset);
|
||||
size_rem -= size;
|
||||
offset += size;
|
||||
}
|
||||
return 0;
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2013 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Intel MIC Host driver.
|
||||
*
|
||||
*/
|
||||
#ifndef _MIC_FOPS_H_
|
||||
#define _MIC_FOPS_H_
|
||||
|
||||
int mic_open(struct inode *inode, struct file *filp);
|
||||
int mic_release(struct inode *inode, struct file *filp);
|
||||
ssize_t mic_read(struct file *filp, char __user *buf,
|
||||
size_t count, loff_t *pos);
|
||||
long mic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
|
||||
int mic_mmap(struct file *f, struct vm_area_struct *vma);
|
||||
unsigned int mic_poll(struct file *f, poll_table *wait);
|
||||
|
||||
#endif
|
@@ -27,8 +27,6 @@
|
||||
#include "mic_device.h"
|
||||
#include "mic_x100.h"
|
||||
#include "mic_smpt.h"
|
||||
#include "mic_fops.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
static const char mic_driver_name[] = "mic";
|
||||
|
||||
@@ -57,17 +55,6 @@ MODULE_DEVICE_TABLE(pci, mic_pci_tbl);
|
||||
|
||||
/* ID allocator for MIC devices */
|
||||
static struct ida g_mic_ida;
|
||||
/* Base device node number for MIC devices */
|
||||
static dev_t g_mic_devno;
|
||||
|
||||
static const struct file_operations mic_fops = {
|
||||
.open = mic_open,
|
||||
.release = mic_release,
|
||||
.unlocked_ioctl = mic_ioctl,
|
||||
.poll = mic_poll,
|
||||
.mmap = mic_mmap,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/* Initialize the device page */
|
||||
static int mic_dp_init(struct mic_device *mdev)
|
||||
@@ -169,7 +156,6 @@ mic_device_init(struct mic_device *mdev, struct pci_dev *pdev)
|
||||
mic_ops_init(mdev);
|
||||
mutex_init(&mdev->mic_mutex);
|
||||
mdev->irq_info.next_avail_src = 0;
|
||||
INIT_LIST_HEAD(&mdev->vdev_list);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,30 +245,15 @@ static int mic_probe(struct pci_dev *pdev,
|
||||
goto smpt_uninit;
|
||||
}
|
||||
mic_bootparam_init(mdev);
|
||||
|
||||
mic_create_debug_dir(mdev);
|
||||
|
||||
mdev->miscdev.minor = MISC_DYNAMIC_MINOR;
|
||||
snprintf(mdev->name, sizeof(mdev->name), "mic%d", mdev->id);
|
||||
mdev->miscdev.name = mdev->name;
|
||||
mdev->miscdev.fops = &mic_fops;
|
||||
mdev->miscdev.parent = &mdev->pdev->dev;
|
||||
rc = misc_register(&mdev->miscdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "misc_register err id %d rc %d\n",
|
||||
mdev->id, rc);
|
||||
goto cleanup_debug_dir;
|
||||
}
|
||||
|
||||
mdev->cosm_dev = cosm_register_device(&mdev->pdev->dev, &cosm_hw_ops);
|
||||
if (IS_ERR(mdev->cosm_dev)) {
|
||||
rc = PTR_ERR(mdev->cosm_dev);
|
||||
dev_err(&pdev->dev, "cosm_add_device failed rc %d\n", rc);
|
||||
goto misc_dereg;
|
||||
goto cleanup_debug_dir;
|
||||
}
|
||||
return 0;
|
||||
misc_dereg:
|
||||
misc_deregister(&mdev->miscdev);
|
||||
cleanup_debug_dir:
|
||||
mic_delete_debug_dir(mdev);
|
||||
mic_dp_uninit(mdev);
|
||||
@@ -323,7 +294,6 @@ static void mic_remove(struct pci_dev *pdev)
|
||||
return;
|
||||
|
||||
cosm_unregister_device(mdev->cosm_dev);
|
||||
misc_deregister(&mdev->miscdev);
|
||||
mic_delete_debug_dir(mdev);
|
||||
mic_dp_uninit(mdev);
|
||||
mic_smpt_uninit(mdev);
|
||||
@@ -347,26 +317,18 @@ static int __init mic_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = alloc_chrdev_region(&g_mic_devno, 0,
|
||||
MIC_MAX_NUM_DEVS, mic_driver_name);
|
||||
if (ret) {
|
||||
pr_err("alloc_chrdev_region failed ret %d\n", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
request_module("mic_x100_dma");
|
||||
mic_init_debugfs();
|
||||
ida_init(&g_mic_ida);
|
||||
ret = pci_register_driver(&mic_driver);
|
||||
if (ret) {
|
||||
pr_err("pci_register_driver failed ret %d\n", ret);
|
||||
goto cleanup_chrdev;
|
||||
goto cleanup_debugfs;
|
||||
}
|
||||
return ret;
|
||||
cleanup_chrdev:
|
||||
return 0;
|
||||
cleanup_debugfs:
|
||||
ida_destroy(&g_mic_ida);
|
||||
mic_exit_debugfs();
|
||||
unregister_chrdev_region(g_mic_devno, MIC_MAX_NUM_DEVS);
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -375,7 +337,6 @@ static void __exit mic_exit(void)
|
||||
pci_unregister_driver(&mic_driver);
|
||||
ida_destroy(&g_mic_ida);
|
||||
mic_exit_debugfs();
|
||||
unregister_chrdev_region(g_mic_devno, MIC_MAX_NUM_DEVS);
|
||||
}
|
||||
|
||||
module_init(mic_init);
|
||||
|
@@ -1,811 +0,0 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2013 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Intel MIC Host driver.
|
||||
*
|
||||
*/
|
||||
#include <linux/pci.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/mic_common.h>
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_smpt.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
/*
|
||||
* Size of the internal buffer used during DMA's as an intermediate buffer
|
||||
* for copy to/from user.
|
||||
*/
|
||||
#define MIC_INT_DMA_BUF_SIZE PAGE_ALIGN(64 * 1024ULL)
|
||||
|
||||
static int mic_sync_dma(struct mic_device *mdev, dma_addr_t dst,
|
||||
dma_addr_t src, size_t len)
|
||||
{
|
||||
int err = 0;
|
||||
struct dma_async_tx_descriptor *tx;
|
||||
struct dma_chan *mic_ch = mdev->dma_ch[0];
|
||||
|
||||
if (!mic_ch) {
|
||||
err = -EBUSY;
|
||||
goto error;
|
||||
}
|
||||
|
||||
tx = mic_ch->device->device_prep_dma_memcpy(mic_ch, dst, src, len,
|
||||
DMA_PREP_FENCE);
|
||||
if (!tx) {
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
} else {
|
||||
dma_cookie_t cookie = tx->tx_submit(tx);
|
||||
|
||||
err = dma_submit_error(cookie);
|
||||
if (err)
|
||||
goto error;
|
||||
err = dma_sync_wait(mic_ch, cookie);
|
||||
}
|
||||
error:
|
||||
if (err)
|
||||
dev_err(&mdev->pdev->dev, "%s %d err %d\n",
|
||||
__func__, __LINE__, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiates the copies across the PCIe bus from card memory to a user
|
||||
* space buffer. When transfers are done using DMA, source/destination
|
||||
* addresses and transfer length must follow the alignment requirements of
|
||||
* the MIC DMA engine.
|
||||
*/
|
||||
static int mic_virtio_copy_to_user(struct mic_vdev *mvdev, void __user *ubuf,
|
||||
size_t len, u64 daddr, size_t dlen,
|
||||
int vr_idx)
|
||||
{
|
||||
struct mic_device *mdev = mvdev->mdev;
|
||||
void __iomem *dbuf = mdev->aper.va + daddr;
|
||||
struct mic_vringh *mvr = &mvdev->mvr[vr_idx];
|
||||
size_t dma_alignment = 1 << mdev->dma_ch[0]->device->copy_align;
|
||||
size_t dma_offset;
|
||||
size_t partlen;
|
||||
int err;
|
||||
|
||||
dma_offset = daddr - round_down(daddr, dma_alignment);
|
||||
daddr -= dma_offset;
|
||||
len += dma_offset;
|
||||
|
||||
while (len) {
|
||||
partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE);
|
||||
|
||||
err = mic_sync_dma(mdev, mvr->buf_da, daddr,
|
||||
ALIGN(partlen, dma_alignment));
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
if (copy_to_user(ubuf, mvr->buf + dma_offset,
|
||||
partlen - dma_offset)) {
|
||||
err = -EFAULT;
|
||||
goto err;
|
||||
}
|
||||
daddr += partlen;
|
||||
ubuf += partlen;
|
||||
dbuf += partlen;
|
||||
mvdev->in_bytes_dma += partlen;
|
||||
mvdev->in_bytes += partlen;
|
||||
len -= partlen;
|
||||
dma_offset = 0;
|
||||
}
|
||||
return 0;
|
||||
err:
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiates copies across the PCIe bus from a user space buffer to card
|
||||
* memory. When transfers are done using DMA, source/destination addresses
|
||||
* and transfer length must follow the alignment requirements of the MIC
|
||||
* DMA engine.
|
||||
*/
|
||||
static int mic_virtio_copy_from_user(struct mic_vdev *mvdev, void __user *ubuf,
|
||||
size_t len, u64 daddr, size_t dlen,
|
||||
int vr_idx)
|
||||
{
|
||||
struct mic_device *mdev = mvdev->mdev;
|
||||
void __iomem *dbuf = mdev->aper.va + daddr;
|
||||
struct mic_vringh *mvr = &mvdev->mvr[vr_idx];
|
||||
size_t dma_alignment = 1 << mdev->dma_ch[0]->device->copy_align;
|
||||
size_t partlen;
|
||||
int err;
|
||||
|
||||
if (daddr & (dma_alignment - 1)) {
|
||||
mvdev->tx_dst_unaligned += len;
|
||||
goto memcpy;
|
||||
} else if (ALIGN(len, dma_alignment) > dlen) {
|
||||
mvdev->tx_len_unaligned += len;
|
||||
goto memcpy;
|
||||
}
|
||||
|
||||
while (len) {
|
||||
partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE);
|
||||
|
||||
if (copy_from_user(mvr->buf, ubuf, partlen)) {
|
||||
err = -EFAULT;
|
||||
goto err;
|
||||
}
|
||||
err = mic_sync_dma(mdev, daddr, mvr->buf_da,
|
||||
ALIGN(partlen, dma_alignment));
|
||||
if (err)
|
||||
goto err;
|
||||
daddr += partlen;
|
||||
ubuf += partlen;
|
||||
dbuf += partlen;
|
||||
mvdev->out_bytes_dma += partlen;
|
||||
mvdev->out_bytes += partlen;
|
||||
len -= partlen;
|
||||
}
|
||||
memcpy:
|
||||
/*
|
||||
* We are copying to IO below and should ideally use something
|
||||
* like copy_from_user_toio(..) if it existed.
|
||||
*/
|
||||
if (copy_from_user((void __force *)dbuf, ubuf, len)) {
|
||||
err = -EFAULT;
|
||||
goto err;
|
||||
}
|
||||
mvdev->out_bytes += len;
|
||||
return 0;
|
||||
err:
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
#define MIC_VRINGH_READ true
|
||||
|
||||
/* The function to call to notify the card about added buffers */
|
||||
static void mic_notify(struct vringh *vrh)
|
||||
{
|
||||
struct mic_vringh *mvrh = container_of(vrh, struct mic_vringh, vrh);
|
||||
struct mic_vdev *mvdev = mvrh->mvdev;
|
||||
s8 db = mvdev->dc->h2c_vdev_db;
|
||||
|
||||
if (db != -1)
|
||||
mvdev->mdev->ops->send_intr(mvdev->mdev, db);
|
||||
}
|
||||
|
||||
/* Determine the total number of bytes consumed in a VRINGH KIOV */
|
||||
static inline u32 mic_vringh_iov_consumed(struct vringh_kiov *iov)
|
||||
{
|
||||
int i;
|
||||
u32 total = iov->consumed;
|
||||
|
||||
for (i = 0; i < iov->i; i++)
|
||||
total += iov->iov[i].iov_len;
|
||||
return total;
|
||||
}
|
||||
|
||||
/*
|
||||
* Traverse the VRINGH KIOV and issue the APIs to trigger the copies.
|
||||
* This API is heavily based on the vringh_iov_xfer(..) implementation
|
||||
* in vringh.c. The reason we cannot reuse vringh_iov_pull_kern(..)
|
||||
* and vringh_iov_push_kern(..) directly is because there is no
|
||||
* way to override the VRINGH xfer(..) routines as of v3.10.
|
||||
*/
|
||||
static int mic_vringh_copy(struct mic_vdev *mvdev, struct vringh_kiov *iov,
|
||||
void __user *ubuf, size_t len, bool read, int vr_idx,
|
||||
size_t *out_len)
|
||||
{
|
||||
int ret = 0;
|
||||
size_t partlen, tot_len = 0;
|
||||
|
||||
while (len && iov->i < iov->used) {
|
||||
partlen = min(iov->iov[iov->i].iov_len, len);
|
||||
if (read)
|
||||
ret = mic_virtio_copy_to_user(mvdev, ubuf, partlen,
|
||||
(u64)iov->iov[iov->i].iov_base,
|
||||
iov->iov[iov->i].iov_len,
|
||||
vr_idx);
|
||||
else
|
||||
ret = mic_virtio_copy_from_user(mvdev, ubuf, partlen,
|
||||
(u64)iov->iov[iov->i].iov_base,
|
||||
iov->iov[iov->i].iov_len,
|
||||
vr_idx);
|
||||
if (ret) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
break;
|
||||
}
|
||||
len -= partlen;
|
||||
ubuf += partlen;
|
||||
tot_len += partlen;
|
||||
iov->consumed += partlen;
|
||||
iov->iov[iov->i].iov_len -= partlen;
|
||||
iov->iov[iov->i].iov_base += partlen;
|
||||
if (!iov->iov[iov->i].iov_len) {
|
||||
/* Fix up old iov element then increment. */
|
||||
iov->iov[iov->i].iov_len = iov->consumed;
|
||||
iov->iov[iov->i].iov_base -= iov->consumed;
|
||||
|
||||
iov->consumed = 0;
|
||||
iov->i++;
|
||||
}
|
||||
}
|
||||
*out_len = tot_len;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the standard VRINGH infrastructure in the kernel to fetch new
|
||||
* descriptors, initiate the copies and update the used ring.
|
||||
*/
|
||||
static int _mic_virtio_copy(struct mic_vdev *mvdev,
|
||||
struct mic_copy_desc *copy)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 iovcnt = copy->iovcnt;
|
||||
struct iovec iov;
|
||||
struct iovec __user *u_iov = copy->iov;
|
||||
void __user *ubuf = NULL;
|
||||
struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx];
|
||||
struct vringh_kiov *riov = &mvr->riov;
|
||||
struct vringh_kiov *wiov = &mvr->wiov;
|
||||
struct vringh *vrh = &mvr->vrh;
|
||||
u16 *head = &mvr->head;
|
||||
struct mic_vring *vr = &mvr->vring;
|
||||
size_t len = 0, out_len;
|
||||
|
||||
copy->out_len = 0;
|
||||
/* Fetch a new IOVEC if all previous elements have been processed */
|
||||
if (riov->i == riov->used && wiov->i == wiov->used) {
|
||||
ret = vringh_getdesc_kern(vrh, riov, wiov,
|
||||
head, GFP_KERNEL);
|
||||
/* Check if there are available descriptors */
|
||||
if (ret <= 0)
|
||||
return ret;
|
||||
}
|
||||
while (iovcnt) {
|
||||
if (!len) {
|
||||
/* Copy over a new iovec from user space. */
|
||||
ret = copy_from_user(&iov, u_iov, sizeof(*u_iov));
|
||||
if (ret) {
|
||||
ret = -EINVAL;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
break;
|
||||
}
|
||||
len = iov.iov_len;
|
||||
ubuf = iov.iov_base;
|
||||
}
|
||||
/* Issue all the read descriptors first */
|
||||
ret = mic_vringh_copy(mvdev, riov, ubuf, len, MIC_VRINGH_READ,
|
||||
copy->vr_idx, &out_len);
|
||||
if (ret) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
break;
|
||||
}
|
||||
len -= out_len;
|
||||
ubuf += out_len;
|
||||
copy->out_len += out_len;
|
||||
/* Issue the write descriptors next */
|
||||
ret = mic_vringh_copy(mvdev, wiov, ubuf, len, !MIC_VRINGH_READ,
|
||||
copy->vr_idx, &out_len);
|
||||
if (ret) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
break;
|
||||
}
|
||||
len -= out_len;
|
||||
ubuf += out_len;
|
||||
copy->out_len += out_len;
|
||||
if (!len) {
|
||||
/* One user space iovec is now completed */
|
||||
iovcnt--;
|
||||
u_iov++;
|
||||
}
|
||||
/* Exit loop if all elements in KIOVs have been processed. */
|
||||
if (riov->i == riov->used && wiov->i == wiov->used)
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Update the used ring if a descriptor was available and some data was
|
||||
* copied in/out and the user asked for a used ring update.
|
||||
*/
|
||||
if (*head != USHRT_MAX && copy->out_len && copy->update_used) {
|
||||
u32 total = 0;
|
||||
|
||||
/* Determine the total data consumed */
|
||||
total += mic_vringh_iov_consumed(riov);
|
||||
total += mic_vringh_iov_consumed(wiov);
|
||||
vringh_complete_kern(vrh, *head, total);
|
||||
*head = USHRT_MAX;
|
||||
if (vringh_need_notify_kern(vrh) > 0)
|
||||
vringh_notify(vrh);
|
||||
vringh_kiov_cleanup(riov);
|
||||
vringh_kiov_cleanup(wiov);
|
||||
/* Update avail idx for user space */
|
||||
vr->info->avail_idx = vrh->last_avail_idx;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int mic_verify_copy_args(struct mic_vdev *mvdev,
|
||||
struct mic_copy_desc *copy)
|
||||
{
|
||||
if (copy->vr_idx >= mvdev->dd->num_vq) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy a specified number of virtio descriptors in a chain */
|
||||
int mic_virtio_copy_desc(struct mic_vdev *mvdev,
|
||||
struct mic_copy_desc *copy)
|
||||
{
|
||||
int err;
|
||||
struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx];
|
||||
|
||||
err = mic_verify_copy_args(mvdev, copy);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mutex_lock(&mvr->vr_mutex);
|
||||
if (!mic_vdevup(mvdev)) {
|
||||
err = -ENODEV;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, err);
|
||||
goto err;
|
||||
}
|
||||
err = _mic_virtio_copy(mvdev, copy);
|
||||
if (err) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, err);
|
||||
}
|
||||
err:
|
||||
mutex_unlock(&mvr->vr_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void mic_virtio_init_post(struct mic_vdev *mvdev)
|
||||
{
|
||||
struct mic_vqconfig *vqconfig = mic_vq_config(mvdev->dd);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++) {
|
||||
if (!le64_to_cpu(vqconfig[i].used_address)) {
|
||||
dev_warn(mic_dev(mvdev), "used_address zero??\n");
|
||||
continue;
|
||||
}
|
||||
mvdev->mvr[i].vrh.vring.used =
|
||||
(void __force *)mvdev->mdev->aper.va +
|
||||
le64_to_cpu(vqconfig[i].used_address);
|
||||
}
|
||||
|
||||
mvdev->dc->used_address_updated = 0;
|
||||
|
||||
dev_dbg(mic_dev(mvdev), "%s: device type %d LINKUP\n",
|
||||
__func__, mvdev->virtio_id);
|
||||
}
|
||||
|
||||
static inline void mic_virtio_device_reset(struct mic_vdev *mvdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
dev_dbg(mic_dev(mvdev), "%s: status %d device type %d RESET\n",
|
||||
__func__, mvdev->dd->status, mvdev->virtio_id);
|
||||
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++)
|
||||
/*
|
||||
* Avoid lockdep false positive. The + 1 is for the mic
|
||||
* mutex which is held in the reset devices code path.
|
||||
*/
|
||||
mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1);
|
||||
|
||||
/* 0 status means "reset" */
|
||||
mvdev->dd->status = 0;
|
||||
mvdev->dc->vdev_reset = 0;
|
||||
mvdev->dc->host_ack = 1;
|
||||
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++) {
|
||||
struct vringh *vrh = &mvdev->mvr[i].vrh;
|
||||
mvdev->mvr[i].vring.info->avail_idx = 0;
|
||||
vrh->completed = 0;
|
||||
vrh->last_avail_idx = 0;
|
||||
vrh->last_used_idx = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++)
|
||||
mutex_unlock(&mvdev->mvr[i].vr_mutex);
|
||||
}
|
||||
|
||||
void mic_virtio_reset_devices(struct mic_device *mdev)
|
||||
{
|
||||
struct list_head *pos, *tmp;
|
||||
struct mic_vdev *mvdev;
|
||||
|
||||
dev_dbg(&mdev->pdev->dev, "%s\n", __func__);
|
||||
|
||||
list_for_each_safe(pos, tmp, &mdev->vdev_list) {
|
||||
mvdev = list_entry(pos, struct mic_vdev, list);
|
||||
mic_virtio_device_reset(mvdev);
|
||||
mvdev->poll_wake = 1;
|
||||
wake_up(&mvdev->waitq);
|
||||
}
|
||||
}
|
||||
|
||||
void mic_bh_handler(struct work_struct *work)
|
||||
{
|
||||
struct mic_vdev *mvdev = container_of(work, struct mic_vdev,
|
||||
virtio_bh_work);
|
||||
|
||||
if (mvdev->dc->used_address_updated)
|
||||
mic_virtio_init_post(mvdev);
|
||||
|
||||
if (mvdev->dc->vdev_reset)
|
||||
mic_virtio_device_reset(mvdev);
|
||||
|
||||
mvdev->poll_wake = 1;
|
||||
wake_up(&mvdev->waitq);
|
||||
}
|
||||
|
||||
static irqreturn_t mic_virtio_intr_handler(int irq, void *data)
|
||||
{
|
||||
struct mic_vdev *mvdev = data;
|
||||
struct mic_device *mdev = mvdev->mdev;
|
||||
|
||||
mdev->ops->intr_workarounds(mdev);
|
||||
schedule_work(&mvdev->virtio_bh_work);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int mic_virtio_config_change(struct mic_vdev *mvdev,
|
||||
void __user *argp)
|
||||
{
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
|
||||
int ret = 0, retry, i;
|
||||
struct mic_bootparam *bootparam = mvdev->mdev->dp;
|
||||
s8 db = bootparam->h2c_config_db;
|
||||
|
||||
mutex_lock(&mvdev->mdev->mic_mutex);
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++)
|
||||
mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1);
|
||||
|
||||
if (db == -1 || mvdev->dd->type == -1) {
|
||||
ret = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (copy_from_user(mic_vq_configspace(mvdev->dd),
|
||||
argp, mvdev->dd->config_len)) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -EFAULT);
|
||||
ret = -EFAULT;
|
||||
goto exit;
|
||||
}
|
||||
mvdev->dc->config_change = MIC_VIRTIO_PARAM_CONFIG_CHANGED;
|
||||
mvdev->mdev->ops->send_intr(mvdev->mdev, db);
|
||||
|
||||
for (retry = 100; retry--;) {
|
||||
ret = wait_event_timeout(wake,
|
||||
mvdev->dc->guest_ack, msecs_to_jiffies(100));
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
dev_dbg(mic_dev(mvdev),
|
||||
"%s %d retry: %d\n", __func__, __LINE__, retry);
|
||||
mvdev->dc->config_change = 0;
|
||||
mvdev->dc->guest_ack = 0;
|
||||
exit:
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++)
|
||||
mutex_unlock(&mvdev->mvr[i].vr_mutex);
|
||||
mutex_unlock(&mvdev->mdev->mic_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mic_copy_dp_entry(struct mic_vdev *mvdev,
|
||||
void __user *argp,
|
||||
__u8 *type,
|
||||
struct mic_device_desc **devpage)
|
||||
{
|
||||
struct mic_device *mdev = mvdev->mdev;
|
||||
struct mic_device_desc dd, *dd_config, *devp;
|
||||
struct mic_vqconfig *vqconfig;
|
||||
int ret = 0, i;
|
||||
bool slot_found = false;
|
||||
|
||||
if (copy_from_user(&dd, argp, sizeof(dd))) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -EFAULT);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (mic_aligned_desc_size(&dd) > MIC_MAX_DESC_BLK_SIZE ||
|
||||
dd.num_vq > MIC_MAX_VRINGS) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dd_config = kmalloc(mic_desc_size(&dd), GFP_KERNEL);
|
||||
if (dd_config == NULL) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -ENOMEM);
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (copy_from_user(dd_config, argp, mic_desc_size(&dd))) {
|
||||
ret = -EFAULT;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
vqconfig = mic_vq_config(dd_config);
|
||||
for (i = 0; i < dd.num_vq; i++) {
|
||||
if (le16_to_cpu(vqconfig[i].num) > MIC_MAX_VRING_ENTRIES) {
|
||||
ret = -EINVAL;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find the first free device page entry */
|
||||
for (i = sizeof(struct mic_bootparam);
|
||||
i < MIC_DP_SIZE - mic_total_desc_size(dd_config);
|
||||
i += mic_total_desc_size(devp)) {
|
||||
devp = mdev->dp + i;
|
||||
if (devp->type == 0 || devp->type == -1) {
|
||||
slot_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!slot_found) {
|
||||
ret = -EINVAL;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
goto exit;
|
||||
}
|
||||
/*
|
||||
* Save off the type before doing the memcpy. Type will be set in the
|
||||
* end after completing all initialization for the new device.
|
||||
*/
|
||||
*type = dd_config->type;
|
||||
dd_config->type = 0;
|
||||
memcpy(devp, dd_config, mic_desc_size(dd_config));
|
||||
|
||||
*devpage = devp;
|
||||
exit:
|
||||
kfree(dd_config);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mic_init_device_ctrl(struct mic_vdev *mvdev,
|
||||
struct mic_device_desc *devpage)
|
||||
{
|
||||
struct mic_device_ctrl *dc;
|
||||
|
||||
dc = (void *)devpage + mic_aligned_desc_size(devpage);
|
||||
|
||||
dc->config_change = 0;
|
||||
dc->guest_ack = 0;
|
||||
dc->vdev_reset = 0;
|
||||
dc->host_ack = 0;
|
||||
dc->used_address_updated = 0;
|
||||
dc->c2h_vdev_db = -1;
|
||||
dc->h2c_vdev_db = -1;
|
||||
mvdev->dc = dc;
|
||||
}
|
||||
|
||||
int mic_virtio_add_device(struct mic_vdev *mvdev,
|
||||
void __user *argp)
|
||||
{
|
||||
struct mic_device *mdev = mvdev->mdev;
|
||||
struct mic_device_desc *dd = NULL;
|
||||
struct mic_vqconfig *vqconfig;
|
||||
int vr_size, i, j, ret;
|
||||
u8 type = 0;
|
||||
s8 db;
|
||||
char irqname[10];
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
u16 num;
|
||||
dma_addr_t vr_addr;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
|
||||
ret = mic_copy_dp_entry(mvdev, argp, &type, &dd);
|
||||
if (ret) {
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mic_init_device_ctrl(mvdev, dd);
|
||||
|
||||
mvdev->dd = dd;
|
||||
mvdev->virtio_id = type;
|
||||
vqconfig = mic_vq_config(dd);
|
||||
INIT_WORK(&mvdev->virtio_bh_work, mic_bh_handler);
|
||||
|
||||
for (i = 0; i < dd->num_vq; i++) {
|
||||
struct mic_vringh *mvr = &mvdev->mvr[i];
|
||||
struct mic_vring *vr = &mvdev->mvr[i].vring;
|
||||
num = le16_to_cpu(vqconfig[i].num);
|
||||
mutex_init(&mvr->vr_mutex);
|
||||
vr_size = PAGE_ALIGN(vring_size(num, MIC_VIRTIO_RING_ALIGN) +
|
||||
sizeof(struct _mic_vring_info));
|
||||
vr->va = (void *)
|
||||
__get_free_pages(GFP_KERNEL | __GFP_ZERO,
|
||||
get_order(vr_size));
|
||||
if (!vr->va) {
|
||||
ret = -ENOMEM;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
goto err;
|
||||
}
|
||||
vr->len = vr_size;
|
||||
vr->info = vr->va + vring_size(num, MIC_VIRTIO_RING_ALIGN);
|
||||
vr->info->magic = cpu_to_le32(MIC_MAGIC + mvdev->virtio_id + i);
|
||||
vr_addr = mic_map_single(mdev, vr->va, vr_size);
|
||||
if (mic_map_error(vr_addr)) {
|
||||
free_pages((unsigned long)vr->va, get_order(vr_size));
|
||||
ret = -ENOMEM;
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
goto err;
|
||||
}
|
||||
vqconfig[i].address = cpu_to_le64(vr_addr);
|
||||
|
||||
vring_init(&vr->vr, num, vr->va, MIC_VIRTIO_RING_ALIGN);
|
||||
ret = vringh_init_kern(&mvr->vrh,
|
||||
*(u32 *)mic_vq_features(mvdev->dd), num, false,
|
||||
vr->vr.desc, vr->vr.avail, vr->vr.used);
|
||||
if (ret) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, ret);
|
||||
goto err;
|
||||
}
|
||||
vringh_kiov_init(&mvr->riov, NULL, 0);
|
||||
vringh_kiov_init(&mvr->wiov, NULL, 0);
|
||||
mvr->head = USHRT_MAX;
|
||||
mvr->mvdev = mvdev;
|
||||
mvr->vrh.notify = mic_notify;
|
||||
dev_dbg(&mdev->pdev->dev,
|
||||
"%s %d index %d va %p info %p vr_size 0x%x\n",
|
||||
__func__, __LINE__, i, vr->va, vr->info, vr_size);
|
||||
mvr->buf = (void *)__get_free_pages(GFP_KERNEL,
|
||||
get_order(MIC_INT_DMA_BUF_SIZE));
|
||||
mvr->buf_da = mic_map_single(mvdev->mdev, mvr->buf,
|
||||
MIC_INT_DMA_BUF_SIZE);
|
||||
}
|
||||
|
||||
snprintf(irqname, sizeof(irqname), "mic%dvirtio%d", mdev->id,
|
||||
mvdev->virtio_id);
|
||||
mvdev->virtio_db = mic_next_db(mdev);
|
||||
mvdev->virtio_cookie = mic_request_threaded_irq(mdev,
|
||||
mic_virtio_intr_handler,
|
||||
NULL, irqname, mvdev,
|
||||
mvdev->virtio_db, MIC_INTR_DB);
|
||||
if (IS_ERR(mvdev->virtio_cookie)) {
|
||||
ret = PTR_ERR(mvdev->virtio_cookie);
|
||||
dev_dbg(&mdev->pdev->dev, "request irq failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
mvdev->dc->c2h_vdev_db = mvdev->virtio_db;
|
||||
|
||||
list_add_tail(&mvdev->list, &mdev->vdev_list);
|
||||
/*
|
||||
* Order the type update with previous stores. This write barrier
|
||||
* is paired with the corresponding read barrier before the uncached
|
||||
* system memory read of the type, on the card while scanning the
|
||||
* device page.
|
||||
*/
|
||||
smp_wmb();
|
||||
dd->type = type;
|
||||
|
||||
dev_dbg(&mdev->pdev->dev, "Added virtio device id %d\n", dd->type);
|
||||
|
||||
db = bootparam->h2c_config_db;
|
||||
if (db != -1)
|
||||
mdev->ops->send_intr(mdev, db);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return 0;
|
||||
err:
|
||||
vqconfig = mic_vq_config(dd);
|
||||
for (j = 0; j < i; j++) {
|
||||
struct mic_vringh *mvr = &mvdev->mvr[j];
|
||||
mic_unmap_single(mdev, le64_to_cpu(vqconfig[j].address),
|
||||
mvr->vring.len);
|
||||
free_pages((unsigned long)mvr->vring.va,
|
||||
get_order(mvr->vring.len));
|
||||
}
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mic_virtio_del_device(struct mic_vdev *mvdev)
|
||||
{
|
||||
struct list_head *pos, *tmp;
|
||||
struct mic_vdev *tmp_mvdev;
|
||||
struct mic_device *mdev = mvdev->mdev;
|
||||
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
|
||||
int i, ret, retry;
|
||||
struct mic_vqconfig *vqconfig;
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
s8 db;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
db = bootparam->h2c_config_db;
|
||||
if (db == -1)
|
||||
goto skip_hot_remove;
|
||||
dev_dbg(&mdev->pdev->dev,
|
||||
"Requesting hot remove id %d\n", mvdev->virtio_id);
|
||||
mvdev->dc->config_change = MIC_VIRTIO_PARAM_DEV_REMOVE;
|
||||
mdev->ops->send_intr(mdev, db);
|
||||
for (retry = 100; retry--;) {
|
||||
ret = wait_event_timeout(wake,
|
||||
mvdev->dc->guest_ack, msecs_to_jiffies(100));
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
dev_dbg(&mdev->pdev->dev,
|
||||
"Device id %d config_change %d guest_ack %d retry %d\n",
|
||||
mvdev->virtio_id, mvdev->dc->config_change,
|
||||
mvdev->dc->guest_ack, retry);
|
||||
mvdev->dc->config_change = 0;
|
||||
mvdev->dc->guest_ack = 0;
|
||||
skip_hot_remove:
|
||||
mic_free_irq(mdev, mvdev->virtio_cookie, mvdev);
|
||||
flush_work(&mvdev->virtio_bh_work);
|
||||
vqconfig = mic_vq_config(mvdev->dd);
|
||||
for (i = 0; i < mvdev->dd->num_vq; i++) {
|
||||
struct mic_vringh *mvr = &mvdev->mvr[i];
|
||||
|
||||
mic_unmap_single(mvdev->mdev, mvr->buf_da,
|
||||
MIC_INT_DMA_BUF_SIZE);
|
||||
free_pages((unsigned long)mvr->buf,
|
||||
get_order(MIC_INT_DMA_BUF_SIZE));
|
||||
vringh_kiov_cleanup(&mvr->riov);
|
||||
vringh_kiov_cleanup(&mvr->wiov);
|
||||
mic_unmap_single(mdev, le64_to_cpu(vqconfig[i].address),
|
||||
mvr->vring.len);
|
||||
free_pages((unsigned long)mvr->vring.va,
|
||||
get_order(mvr->vring.len));
|
||||
}
|
||||
|
||||
list_for_each_safe(pos, tmp, &mdev->vdev_list) {
|
||||
tmp_mvdev = list_entry(pos, struct mic_vdev, list);
|
||||
if (tmp_mvdev == mvdev) {
|
||||
list_del(pos);
|
||||
dev_dbg(&mdev->pdev->dev,
|
||||
"Removing virtio device id %d\n",
|
||||
mvdev->virtio_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Order the type update with previous stores. This write barrier
|
||||
* is paired with the corresponding read barrier before the uncached
|
||||
* system memory read of the type, on the card while scanning the
|
||||
* device page.
|
||||
*/
|
||||
smp_wmb();
|
||||
mvdev->dd->type = -1;
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
}
|
@@ -450,26 +450,29 @@ mic_x100_load_firmware(struct mic_device *mdev, const char *buf)
|
||||
|
||||
rc = mic_x100_get_boot_addr(mdev);
|
||||
if (rc)
|
||||
goto error;
|
||||
return rc;
|
||||
/* load OS */
|
||||
rc = request_firmware(&fw, mdev->cosm_dev->firmware, &mdev->pdev->dev);
|
||||
if (rc < 0) {
|
||||
dev_err(&mdev->pdev->dev,
|
||||
"ramdisk request_firmware failed: %d %s\n",
|
||||
rc, mdev->cosm_dev->firmware);
|
||||
goto error;
|
||||
return rc;
|
||||
}
|
||||
if (mdev->bootaddr > mdev->aper.len - fw->size) {
|
||||
rc = -EINVAL;
|
||||
dev_err(&mdev->pdev->dev, "%s %d rc %d bootaddr 0x%x\n",
|
||||
__func__, __LINE__, rc, mdev->bootaddr);
|
||||
release_firmware(fw);
|
||||
goto error;
|
||||
}
|
||||
memcpy_toio(mdev->aper.va + mdev->bootaddr, fw->data, fw->size);
|
||||
mdev->ops->write_spad(mdev, MIC_X100_FW_SIZE, fw->size);
|
||||
if (!strcmp(mdev->cosm_dev->bootmode, "flash"))
|
||||
goto done;
|
||||
if (!strcmp(mdev->cosm_dev->bootmode, "flash")) {
|
||||
rc = -EINVAL;
|
||||
dev_err(&mdev->pdev->dev, "%s %d rc %d\n",
|
||||
__func__, __LINE__, rc);
|
||||
goto error;
|
||||
}
|
||||
/* load command line */
|
||||
rc = mic_x100_load_command_line(mdev, fw);
|
||||
if (rc) {
|
||||
@@ -481,9 +484,11 @@ mic_x100_load_firmware(struct mic_device *mdev, const char *buf)
|
||||
/* load ramdisk */
|
||||
if (mdev->cosm_dev->ramdisk)
|
||||
rc = mic_x100_load_ramdisk(mdev);
|
||||
|
||||
return rc;
|
||||
|
||||
error:
|
||||
dev_dbg(&mdev->pdev->dev, "%s %d rc %d\n", __func__, __LINE__, rc);
|
||||
done:
|
||||
release_firmware(fw);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@@ -74,11 +74,6 @@ struct scif_copy_work {
|
||||
bool ordered;
|
||||
};
|
||||
|
||||
#ifndef list_entry_next
|
||||
#define list_entry_next(pos, member) \
|
||||
list_entry(pos->member.next, typeof(*pos), member)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* scif_reserve_dma_chan:
|
||||
* @ep: Endpoint Descriptor.
|
||||
@@ -276,13 +271,10 @@ static struct scif_mmu_notif *
|
||||
scif_find_mmu_notifier(struct mm_struct *mm, struct scif_endpt_rma_info *rma)
|
||||
{
|
||||
struct scif_mmu_notif *mmn;
|
||||
struct list_head *item;
|
||||
|
||||
list_for_each(item, &rma->mmn_list) {
|
||||
mmn = list_entry(item, struct scif_mmu_notif, list);
|
||||
list_for_each_entry(mmn, &rma->mmn_list, list)
|
||||
if (mmn->mm == mm)
|
||||
return mmn;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -293,13 +285,12 @@ scif_add_mmu_notifier(struct mm_struct *mm, struct scif_endpt *ep)
|
||||
= kzalloc(sizeof(*mmn), GFP_KERNEL);
|
||||
|
||||
if (!mmn)
|
||||
return ERR_PTR(ENOMEM);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
scif_init_mmu_notifier(mmn, current->mm, ep);
|
||||
if (mmu_notifier_register(&mmn->ep_mmu_notifier,
|
||||
current->mm)) {
|
||||
if (mmu_notifier_register(&mmn->ep_mmu_notifier, current->mm)) {
|
||||
kfree(mmn);
|
||||
return ERR_PTR(EBUSY);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
list_add(&mmn->list, &ep->rma_info.mmn_list);
|
||||
return mmn;
|
||||
@@ -851,7 +842,7 @@ static void scif_rma_local_cpu_copy(s64 offset, struct scif_window *window,
|
||||
(window->nr_pages << PAGE_SHIFT);
|
||||
while (rem_len) {
|
||||
if (offset == end_offset) {
|
||||
window = list_entry_next(window, list);
|
||||
window = list_next_entry(window, list);
|
||||
end_offset = window->offset +
|
||||
(window->nr_pages << PAGE_SHIFT);
|
||||
}
|
||||
@@ -957,7 +948,7 @@ scif_rma_list_dma_copy_unaligned(struct scif_copy_work *work,
|
||||
remaining_len -= tail_len;
|
||||
while (remaining_len) {
|
||||
if (offset == end_offset) {
|
||||
window = list_entry_next(window, list);
|
||||
window = list_next_entry(window, list);
|
||||
end_offset = window->offset +
|
||||
(window->nr_pages << PAGE_SHIFT);
|
||||
}
|
||||
@@ -1064,7 +1055,7 @@ scif_rma_list_dma_copy_unaligned(struct scif_copy_work *work,
|
||||
}
|
||||
if (tail_len) {
|
||||
if (offset == end_offset) {
|
||||
window = list_entry_next(window, list);
|
||||
window = list_next_entry(window, list);
|
||||
end_offset = window->offset +
|
||||
(window->nr_pages << PAGE_SHIFT);
|
||||
}
|
||||
@@ -1147,13 +1138,13 @@ static int _scif_rma_list_dma_copy_aligned(struct scif_copy_work *work,
|
||||
(dst_window->nr_pages << PAGE_SHIFT);
|
||||
while (remaining_len) {
|
||||
if (src_offset == end_src_offset) {
|
||||
src_window = list_entry_next(src_window, list);
|
||||
src_window = list_next_entry(src_window, list);
|
||||
end_src_offset = src_window->offset +
|
||||
(src_window->nr_pages << PAGE_SHIFT);
|
||||
scif_init_window_iter(src_window, &src_win_iter);
|
||||
}
|
||||
if (dst_offset == end_dst_offset) {
|
||||
dst_window = list_entry_next(dst_window, list);
|
||||
dst_window = list_next_entry(dst_window, list);
|
||||
end_dst_offset = dst_window->offset +
|
||||
(dst_window->nr_pages << PAGE_SHIFT);
|
||||
scif_init_window_iter(dst_window, &dst_win_iter);
|
||||
@@ -1314,13 +1305,13 @@ static int scif_rma_list_dma_copy_aligned(struct scif_copy_work *work,
|
||||
remaining_len -= tail_len;
|
||||
while (remaining_len) {
|
||||
if (src_offset == end_src_offset) {
|
||||
src_window = list_entry_next(src_window, list);
|
||||
src_window = list_next_entry(src_window, list);
|
||||
end_src_offset = src_window->offset +
|
||||
(src_window->nr_pages << PAGE_SHIFT);
|
||||
scif_init_window_iter(src_window, &src_win_iter);
|
||||
}
|
||||
if (dst_offset == end_dst_offset) {
|
||||
dst_window = list_entry_next(dst_window, list);
|
||||
dst_window = list_next_entry(dst_window, list);
|
||||
end_dst_offset = dst_window->offset +
|
||||
(dst_window->nr_pages << PAGE_SHIFT);
|
||||
scif_init_window_iter(dst_window, &dst_win_iter);
|
||||
@@ -1405,9 +1396,9 @@ static int scif_rma_list_dma_copy_aligned(struct scif_copy_work *work,
|
||||
if (remaining_len) {
|
||||
loop_len = remaining_len;
|
||||
if (src_offset == end_src_offset)
|
||||
src_window = list_entry_next(src_window, list);
|
||||
src_window = list_next_entry(src_window, list);
|
||||
if (dst_offset == end_dst_offset)
|
||||
dst_window = list_entry_next(dst_window, list);
|
||||
dst_window = list_next_entry(dst_window, list);
|
||||
|
||||
src_dma_addr = __scif_off_to_dma_addr(src_window, src_offset);
|
||||
dst_dma_addr = __scif_off_to_dma_addr(dst_window, dst_offset);
|
||||
@@ -1550,12 +1541,12 @@ static int scif_rma_list_cpu_copy(struct scif_copy_work *work)
|
||||
end_dst_offset = dst_window->offset +
|
||||
(dst_window->nr_pages << PAGE_SHIFT);
|
||||
if (src_offset == end_src_offset) {
|
||||
src_window = list_entry_next(src_window, list);
|
||||
src_window = list_next_entry(src_window, list);
|
||||
scif_init_window_iter(src_window,
|
||||
&src_win_iter);
|
||||
}
|
||||
if (dst_offset == end_dst_offset) {
|
||||
dst_window = list_entry_next(dst_window, list);
|
||||
dst_window = list_next_entry(dst_window, list);
|
||||
scif_init_window_iter(dst_window,
|
||||
&dst_win_iter);
|
||||
}
|
||||
@@ -1730,7 +1721,7 @@ static int scif_rma_copy(scif_epd_t epd, off_t loffset, unsigned long addr,
|
||||
mutex_lock(&ep->rma_info.mmn_lock);
|
||||
mmn = scif_find_mmu_notifier(current->mm, &ep->rma_info);
|
||||
if (!mmn)
|
||||
scif_add_mmu_notifier(current->mm, ep);
|
||||
mmn = scif_add_mmu_notifier(current->mm, ep);
|
||||
mutex_unlock(&ep->rma_info.mmn_lock);
|
||||
if (IS_ERR(mmn)) {
|
||||
scif_put_peer_dev(spdev);
|
||||
|
@@ -1511,7 +1511,7 @@ off_t scif_register_pinned_pages(scif_epd_t epd,
|
||||
if ((map_flags & SCIF_MAP_FIXED) &&
|
||||
((ALIGN(offset, PAGE_SIZE) != offset) ||
|
||||
(offset < 0) ||
|
||||
(offset + (off_t)len < offset)))
|
||||
(len > LONG_MAX - offset)))
|
||||
return -EINVAL;
|
||||
|
||||
might_sleep();
|
||||
@@ -1614,7 +1614,7 @@ off_t scif_register(scif_epd_t epd, void *addr, size_t len, off_t offset,
|
||||
if ((map_flags & SCIF_MAP_FIXED) &&
|
||||
((ALIGN(offset, PAGE_SIZE) != offset) ||
|
||||
(offset < 0) ||
|
||||
(offset + (off_t)len < offset)))
|
||||
(len > LONG_MAX - offset)))
|
||||
return -EINVAL;
|
||||
|
||||
/* Unsupported protection requested */
|
||||
@@ -1732,7 +1732,8 @@ scif_unregister(scif_epd_t epd, off_t offset, size_t len)
|
||||
|
||||
/* Offset is not page aligned or offset+len wraps around */
|
||||
if ((ALIGN(offset, PAGE_SIZE) != offset) ||
|
||||
(offset + (off_t)len < offset))
|
||||
(offset < 0) ||
|
||||
(len > LONG_MAX - offset))
|
||||
return -EINVAL;
|
||||
|
||||
err = scif_verify_epd(ep);
|
||||
|
9
drivers/misc/mic/vop/Makefile
Normal file
9
drivers/misc/mic/vop/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# Makefile - Intel MIC Linux driver.
|
||||
# Copyright(c) 2016, Intel Corporation.
|
||||
#
|
||||
obj-m := vop.o
|
||||
|
||||
vop-objs += vop_main.o
|
||||
vop-objs += vop_debugfs.o
|
||||
vop-objs += vop_vringh.o
|
232
drivers/misc/mic/vop/vop_debugfs.c
Normal file
232
drivers/misc/mic/vop/vop_debugfs.c
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2016 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Intel Virtio Over PCIe (VOP) driver.
|
||||
*
|
||||
*/
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include "vop_main.h"
|
||||
|
||||
static int vop_dp_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
struct mic_device_desc *d;
|
||||
struct mic_device_ctrl *dc;
|
||||
struct mic_vqconfig *vqconfig;
|
||||
__u32 *features;
|
||||
__u8 *config;
|
||||
struct vop_info *vi = s->private;
|
||||
struct vop_device *vpdev = vi->vpdev;
|
||||
struct mic_bootparam *bootparam = vpdev->hw_ops->get_dp(vpdev);
|
||||
int j, k;
|
||||
|
||||
seq_printf(s, "Bootparam: magic 0x%x\n",
|
||||
bootparam->magic);
|
||||
seq_printf(s, "Bootparam: h2c_config_db %d\n",
|
||||
bootparam->h2c_config_db);
|
||||
seq_printf(s, "Bootparam: node_id %d\n",
|
||||
bootparam->node_id);
|
||||
seq_printf(s, "Bootparam: c2h_scif_db %d\n",
|
||||
bootparam->c2h_scif_db);
|
||||
seq_printf(s, "Bootparam: h2c_scif_db %d\n",
|
||||
bootparam->h2c_scif_db);
|
||||
seq_printf(s, "Bootparam: scif_host_dma_addr 0x%llx\n",
|
||||
bootparam->scif_host_dma_addr);
|
||||
seq_printf(s, "Bootparam: scif_card_dma_addr 0x%llx\n",
|
||||
bootparam->scif_card_dma_addr);
|
||||
|
||||
for (j = sizeof(*bootparam);
|
||||
j < MIC_DP_SIZE; j += mic_total_desc_size(d)) {
|
||||
d = (void *)bootparam + j;
|
||||
dc = (void *)d + mic_aligned_desc_size(d);
|
||||
|
||||
/* end of list */
|
||||
if (d->type == 0)
|
||||
break;
|
||||
|
||||
if (d->type == -1)
|
||||
continue;
|
||||
|
||||
seq_printf(s, "Type %d ", d->type);
|
||||
seq_printf(s, "Num VQ %d ", d->num_vq);
|
||||
seq_printf(s, "Feature Len %d\n", d->feature_len);
|
||||
seq_printf(s, "Config Len %d ", d->config_len);
|
||||
seq_printf(s, "Shutdown Status %d\n", d->status);
|
||||
|
||||
for (k = 0; k < d->num_vq; k++) {
|
||||
vqconfig = mic_vq_config(d) + k;
|
||||
seq_printf(s, "vqconfig[%d]: ", k);
|
||||
seq_printf(s, "address 0x%llx ",
|
||||
vqconfig->address);
|
||||
seq_printf(s, "num %d ", vqconfig->num);
|
||||
seq_printf(s, "used address 0x%llx\n",
|
||||
vqconfig->used_address);
|
||||
}
|
||||
|
||||
features = (__u32 *)mic_vq_features(d);
|
||||
seq_printf(s, "Features: Host 0x%x ", features[0]);
|
||||
seq_printf(s, "Guest 0x%x\n", features[1]);
|
||||
|
||||
config = mic_vq_configspace(d);
|
||||
for (k = 0; k < d->config_len; k++)
|
||||
seq_printf(s, "config[%d]=%d\n", k, config[k]);
|
||||
|
||||
seq_puts(s, "Device control:\n");
|
||||
seq_printf(s, "Config Change %d ", dc->config_change);
|
||||
seq_printf(s, "Vdev reset %d\n", dc->vdev_reset);
|
||||
seq_printf(s, "Guest Ack %d ", dc->guest_ack);
|
||||
seq_printf(s, "Host ack %d\n", dc->host_ack);
|
||||
seq_printf(s, "Used address updated %d ",
|
||||
dc->used_address_updated);
|
||||
seq_printf(s, "Vdev 0x%llx\n", dc->vdev);
|
||||
seq_printf(s, "c2h doorbell %d ", dc->c2h_vdev_db);
|
||||
seq_printf(s, "h2c doorbell %d\n", dc->h2c_vdev_db);
|
||||
}
|
||||
schedule_work(&vi->hotplug_work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vop_dp_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, vop_dp_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int vop_dp_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations dp_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = vop_dp_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = vop_dp_debug_release
|
||||
};
|
||||
|
||||
static int vop_vdev_info_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct vop_info *vi = s->private;
|
||||
struct list_head *pos, *tmp;
|
||||
struct vop_vdev *vdev;
|
||||
int i, j;
|
||||
|
||||
mutex_lock(&vi->vop_mutex);
|
||||
list_for_each_safe(pos, tmp, &vi->vdev_list) {
|
||||
vdev = list_entry(pos, struct vop_vdev, list);
|
||||
seq_printf(s, "VDEV type %d state %s in %ld out %ld in_dma %ld out_dma %ld\n",
|
||||
vdev->virtio_id,
|
||||
vop_vdevup(vdev) ? "UP" : "DOWN",
|
||||
vdev->in_bytes,
|
||||
vdev->out_bytes,
|
||||
vdev->in_bytes_dma,
|
||||
vdev->out_bytes_dma);
|
||||
for (i = 0; i < MIC_MAX_VRINGS; i++) {
|
||||
struct vring_desc *desc;
|
||||
struct vring_avail *avail;
|
||||
struct vring_used *used;
|
||||
struct vop_vringh *vvr = &vdev->vvr[i];
|
||||
struct vringh *vrh = &vvr->vrh;
|
||||
int num = vrh->vring.num;
|
||||
|
||||
if (!num)
|
||||
continue;
|
||||
desc = vrh->vring.desc;
|
||||
seq_printf(s, "vring i %d avail_idx %d",
|
||||
i, vvr->vring.info->avail_idx & (num - 1));
|
||||
seq_printf(s, " vring i %d avail_idx %d\n",
|
||||
i, vvr->vring.info->avail_idx);
|
||||
seq_printf(s, "vrh i %d weak_barriers %d",
|
||||
i, vrh->weak_barriers);
|
||||
seq_printf(s, " last_avail_idx %d last_used_idx %d",
|
||||
vrh->last_avail_idx, vrh->last_used_idx);
|
||||
seq_printf(s, " completed %d\n", vrh->completed);
|
||||
for (j = 0; j < num; j++) {
|
||||
seq_printf(s, "desc[%d] addr 0x%llx len %d",
|
||||
j, desc->addr, desc->len);
|
||||
seq_printf(s, " flags 0x%x next %d\n",
|
||||
desc->flags, desc->next);
|
||||
desc++;
|
||||
}
|
||||
avail = vrh->vring.avail;
|
||||
seq_printf(s, "avail flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, avail->flags),
|
||||
vringh16_to_cpu(vrh,
|
||||
avail->idx) & (num - 1));
|
||||
seq_printf(s, "avail flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, avail->flags),
|
||||
vringh16_to_cpu(vrh, avail->idx));
|
||||
for (j = 0; j < num; j++)
|
||||
seq_printf(s, "avail ring[%d] %d\n",
|
||||
j, avail->ring[j]);
|
||||
used = vrh->vring.used;
|
||||
seq_printf(s, "used flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, used->flags),
|
||||
vringh16_to_cpu(vrh, used->idx) & (num - 1));
|
||||
seq_printf(s, "used flags 0x%x idx %d\n",
|
||||
vringh16_to_cpu(vrh, used->flags),
|
||||
vringh16_to_cpu(vrh, used->idx));
|
||||
for (j = 0; j < num; j++)
|
||||
seq_printf(s, "used ring[%d] id %d len %d\n",
|
||||
j, vringh32_to_cpu(vrh,
|
||||
used->ring[j].id),
|
||||
vringh32_to_cpu(vrh,
|
||||
used->ring[j].len));
|
||||
}
|
||||
}
|
||||
mutex_unlock(&vi->vop_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vop_vdev_info_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, vop_vdev_info_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int vop_vdev_info_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations vdev_info_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = vop_vdev_info_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = vop_vdev_info_debug_release
|
||||
};
|
||||
|
||||
void vop_init_debugfs(struct vop_info *vi)
|
||||
{
|
||||
char name[16];
|
||||
|
||||
snprintf(name, sizeof(name), "%s%d", KBUILD_MODNAME, vi->vpdev->dnode);
|
||||
vi->dbg = debugfs_create_dir(name, NULL);
|
||||
if (!vi->dbg) {
|
||||
pr_err("can't create debugfs dir vop\n");
|
||||
return;
|
||||
}
|
||||
debugfs_create_file("dp", 0444, vi->dbg, vi, &dp_ops);
|
||||
debugfs_create_file("vdev_info", 0444, vi->dbg, vi, &vdev_info_ops);
|
||||
}
|
||||
|
||||
void vop_exit_debugfs(struct vop_info *vi)
|
||||
{
|
||||
debugfs_remove_recursive(vi->dbg);
|
||||
}
|
755
drivers/misc/mic/vop/vop_main.c
Normal file
755
drivers/misc/mic/vop/vop_main.c
Normal file
@@ -0,0 +1,755 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2016 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Adapted from:
|
||||
*
|
||||
* virtio for kvm on s390
|
||||
*
|
||||
* Copyright IBM Corp. 2008
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License (version 2 only)
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* Author(s): Christian Borntraeger <borntraeger@de.ibm.com>
|
||||
*
|
||||
* Intel Virtio Over PCIe (VOP) driver.
|
||||
*
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include "vop_main.h"
|
||||
|
||||
#define VOP_MAX_VRINGS 4
|
||||
|
||||
/*
|
||||
* _vop_vdev - Allocated per virtio device instance injected by the peer.
|
||||
*
|
||||
* @vdev: Virtio device
|
||||
* @desc: Virtio device page descriptor
|
||||
* @dc: Virtio device control
|
||||
* @vpdev: VOP device which is the parent for this virtio device
|
||||
* @vr: Buffer for accessing the VRING
|
||||
* @used: Buffer for used
|
||||
* @used_size: Size of the used buffer
|
||||
* @reset_done: Track whether VOP reset is complete
|
||||
* @virtio_cookie: Cookie returned upon requesting a interrupt
|
||||
* @c2h_vdev_db: The doorbell used by the guest to interrupt the host
|
||||
* @h2c_vdev_db: The doorbell used by the host to interrupt the guest
|
||||
* @dnode: The destination node
|
||||
*/
|
||||
struct _vop_vdev {
|
||||
struct virtio_device vdev;
|
||||
struct mic_device_desc __iomem *desc;
|
||||
struct mic_device_ctrl __iomem *dc;
|
||||
struct vop_device *vpdev;
|
||||
void __iomem *vr[VOP_MAX_VRINGS];
|
||||
dma_addr_t used[VOP_MAX_VRINGS];
|
||||
int used_size[VOP_MAX_VRINGS];
|
||||
struct completion reset_done;
|
||||
struct mic_irq *virtio_cookie;
|
||||
int c2h_vdev_db;
|
||||
int h2c_vdev_db;
|
||||
int dnode;
|
||||
};
|
||||
|
||||
#define to_vopvdev(vd) container_of(vd, struct _vop_vdev, vdev)
|
||||
|
||||
#define _vop_aligned_desc_size(d) __mic_align(_vop_desc_size(d), 8)
|
||||
|
||||
/* Helper API to obtain the parent of the virtio device */
|
||||
static inline struct device *_vop_dev(struct _vop_vdev *vdev)
|
||||
{
|
||||
return vdev->vdev.dev.parent;
|
||||
}
|
||||
|
||||
static inline unsigned _vop_desc_size(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return sizeof(*desc)
|
||||
+ ioread8(&desc->num_vq) * sizeof(struct mic_vqconfig)
|
||||
+ ioread8(&desc->feature_len) * 2
|
||||
+ ioread8(&desc->config_len);
|
||||
}
|
||||
|
||||
static inline struct mic_vqconfig __iomem *
|
||||
_vop_vq_config(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return (struct mic_vqconfig __iomem *)(desc + 1);
|
||||
}
|
||||
|
||||
static inline u8 __iomem *
|
||||
_vop_vq_features(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return (u8 __iomem *)(_vop_vq_config(desc) + ioread8(&desc->num_vq));
|
||||
}
|
||||
|
||||
static inline u8 __iomem *
|
||||
_vop_vq_configspace(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return _vop_vq_features(desc) + ioread8(&desc->feature_len) * 2;
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
_vop_total_desc_size(struct mic_device_desc __iomem *desc)
|
||||
{
|
||||
return _vop_aligned_desc_size(desc) + sizeof(struct mic_device_ctrl);
|
||||
}
|
||||
|
||||
/* This gets the device's feature bits. */
|
||||
static u64 vop_get_features(struct virtio_device *vdev)
|
||||
{
|
||||
unsigned int i, bits;
|
||||
u32 features = 0;
|
||||
struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc;
|
||||
u8 __iomem *in_features = _vop_vq_features(desc);
|
||||
int feature_len = ioread8(&desc->feature_len);
|
||||
|
||||
bits = min_t(unsigned, feature_len, sizeof(vdev->features)) * 8;
|
||||
for (i = 0; i < bits; i++)
|
||||
if (ioread8(&in_features[i / 8]) & (BIT(i % 8)))
|
||||
features |= BIT(i);
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
static int vop_finalize_features(struct virtio_device *vdev)
|
||||
{
|
||||
unsigned int i, bits;
|
||||
struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc;
|
||||
u8 feature_len = ioread8(&desc->feature_len);
|
||||
/* Second half of bitmap is features we accept. */
|
||||
u8 __iomem *out_features =
|
||||
_vop_vq_features(desc) + feature_len;
|
||||
|
||||
/* Give virtio_ring a chance to accept features. */
|
||||
vring_transport_features(vdev);
|
||||
|
||||
memset_io(out_features, 0, feature_len);
|
||||
bits = min_t(unsigned, feature_len,
|
||||
sizeof(vdev->features)) * 8;
|
||||
for (i = 0; i < bits; i++) {
|
||||
if (__virtio_test_bit(vdev, i))
|
||||
iowrite8(ioread8(&out_features[i / 8]) | (1 << (i % 8)),
|
||||
&out_features[i / 8]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reading and writing elements in config space
|
||||
*/
|
||||
static void vop_get(struct virtio_device *vdev, unsigned int offset,
|
||||
void *buf, unsigned len)
|
||||
{
|
||||
struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc;
|
||||
|
||||
if (offset + len > ioread8(&desc->config_len))
|
||||
return;
|
||||
memcpy_fromio(buf, _vop_vq_configspace(desc) + offset, len);
|
||||
}
|
||||
|
||||
static void vop_set(struct virtio_device *vdev, unsigned int offset,
|
||||
const void *buf, unsigned len)
|
||||
{
|
||||
struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc;
|
||||
|
||||
if (offset + len > ioread8(&desc->config_len))
|
||||
return;
|
||||
memcpy_toio(_vop_vq_configspace(desc) + offset, buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* The operations to get and set the status word just access the status
|
||||
* field of the device descriptor. set_status also interrupts the host
|
||||
* to tell about status changes.
|
||||
*/
|
||||
static u8 vop_get_status(struct virtio_device *vdev)
|
||||
{
|
||||
return ioread8(&to_vopvdev(vdev)->desc->status);
|
||||
}
|
||||
|
||||
static void vop_set_status(struct virtio_device *dev, u8 status)
|
||||
{
|
||||
struct _vop_vdev *vdev = to_vopvdev(dev);
|
||||
struct vop_device *vpdev = vdev->vpdev;
|
||||
|
||||
if (!status)
|
||||
return;
|
||||
iowrite8(status, &vdev->desc->status);
|
||||
vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db);
|
||||
}
|
||||
|
||||
/* Inform host on a virtio device reset and wait for ack from host */
|
||||
static void vop_reset_inform_host(struct virtio_device *dev)
|
||||
{
|
||||
struct _vop_vdev *vdev = to_vopvdev(dev);
|
||||
struct mic_device_ctrl __iomem *dc = vdev->dc;
|
||||
struct vop_device *vpdev = vdev->vpdev;
|
||||
int retry;
|
||||
|
||||
iowrite8(0, &dc->host_ack);
|
||||
iowrite8(1, &dc->vdev_reset);
|
||||
vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db);
|
||||
|
||||
/* Wait till host completes all card accesses and acks the reset */
|
||||
for (retry = 100; retry--;) {
|
||||
if (ioread8(&dc->host_ack))
|
||||
break;
|
||||
msleep(100);
|
||||
};
|
||||
|
||||
dev_dbg(_vop_dev(vdev), "%s: retry: %d\n", __func__, retry);
|
||||
|
||||
/* Reset status to 0 in case we timed out */
|
||||
iowrite8(0, &vdev->desc->status);
|
||||
}
|
||||
|
||||
static void vop_reset(struct virtio_device *dev)
|
||||
{
|
||||
struct _vop_vdev *vdev = to_vopvdev(dev);
|
||||
|
||||
dev_dbg(_vop_dev(vdev), "%s: virtio id %d\n",
|
||||
__func__, dev->id.device);
|
||||
|
||||
vop_reset_inform_host(dev);
|
||||
complete_all(&vdev->reset_done);
|
||||
}
|
||||
|
||||
/*
|
||||
* The virtio_ring code calls this API when it wants to notify the Host.
|
||||
*/
|
||||
static bool vop_notify(struct virtqueue *vq)
|
||||
{
|
||||
struct _vop_vdev *vdev = vq->priv;
|
||||
struct vop_device *vpdev = vdev->vpdev;
|
||||
|
||||
vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void vop_del_vq(struct virtqueue *vq, int n)
|
||||
{
|
||||
struct _vop_vdev *vdev = to_vopvdev(vq->vdev);
|
||||
struct vring *vr = (struct vring *)(vq + 1);
|
||||
struct vop_device *vpdev = vdev->vpdev;
|
||||
|
||||
dma_unmap_single(&vpdev->dev, vdev->used[n],
|
||||
vdev->used_size[n], DMA_BIDIRECTIONAL);
|
||||
free_pages((unsigned long)vr->used, get_order(vdev->used_size[n]));
|
||||
vring_del_virtqueue(vq);
|
||||
vpdev->hw_ops->iounmap(vpdev, vdev->vr[n]);
|
||||
vdev->vr[n] = NULL;
|
||||
}
|
||||
|
||||
static void vop_del_vqs(struct virtio_device *dev)
|
||||
{
|
||||
struct _vop_vdev *vdev = to_vopvdev(dev);
|
||||
struct virtqueue *vq, *n;
|
||||
int idx = 0;
|
||||
|
||||
dev_dbg(_vop_dev(vdev), "%s\n", __func__);
|
||||
|
||||
list_for_each_entry_safe(vq, n, &dev->vqs, list)
|
||||
vop_del_vq(vq, idx++);
|
||||
}
|
||||
|
||||
/*
|
||||
* This routine will assign vring's allocated in host/io memory. Code in
|
||||
* virtio_ring.c however continues to access this io memory as if it were local
|
||||
* memory without io accessors.
|
||||
*/
|
||||
static struct virtqueue *vop_find_vq(struct virtio_device *dev,
|
||||
unsigned index,
|
||||
void (*callback)(struct virtqueue *vq),
|
||||
const char *name)
|
||||
{
|
||||
struct _vop_vdev *vdev = to_vopvdev(dev);
|
||||
struct vop_device *vpdev = vdev->vpdev;
|
||||
struct mic_vqconfig __iomem *vqconfig;
|
||||
struct mic_vqconfig config;
|
||||
struct virtqueue *vq;
|
||||
void __iomem *va;
|
||||
struct _mic_vring_info __iomem *info;
|
||||
void *used;
|
||||
int vr_size, _vr_size, err, magic;
|
||||
struct vring *vr;
|
||||
u8 type = ioread8(&vdev->desc->type);
|
||||
|
||||
if (index >= ioread8(&vdev->desc->num_vq))
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if (!name)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
/* First assign the vring's allocated in host memory */
|
||||
vqconfig = _vop_vq_config(vdev->desc) + index;
|
||||
memcpy_fromio(&config, vqconfig, sizeof(config));
|
||||
_vr_size = vring_size(le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN);
|
||||
vr_size = PAGE_ALIGN(_vr_size + sizeof(struct _mic_vring_info));
|
||||
va = vpdev->hw_ops->ioremap(vpdev, le64_to_cpu(config.address),
|
||||
vr_size);
|
||||
if (!va)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
vdev->vr[index] = va;
|
||||
memset_io(va, 0x0, _vr_size);
|
||||
vq = vring_new_virtqueue(
|
||||
index,
|
||||
le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN,
|
||||
dev,
|
||||
false,
|
||||
(void __force *)va, vop_notify, callback, name);
|
||||
if (!vq) {
|
||||
err = -ENOMEM;
|
||||
goto unmap;
|
||||
}
|
||||
info = va + _vr_size;
|
||||
magic = ioread32(&info->magic);
|
||||
|
||||
if (WARN(magic != MIC_MAGIC + type + index, "magic mismatch")) {
|
||||
err = -EIO;
|
||||
goto unmap;
|
||||
}
|
||||
|
||||
/* Allocate and reassign used ring now */
|
||||
vdev->used_size[index] = PAGE_ALIGN(sizeof(__u16) * 3 +
|
||||
sizeof(struct vring_used_elem) *
|
||||
le16_to_cpu(config.num));
|
||||
used = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
|
||||
get_order(vdev->used_size[index]));
|
||||
if (!used) {
|
||||
err = -ENOMEM;
|
||||
dev_err(_vop_dev(vdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, err);
|
||||
goto del_vq;
|
||||
}
|
||||
vdev->used[index] = dma_map_single(&vpdev->dev, used,
|
||||
vdev->used_size[index],
|
||||
DMA_BIDIRECTIONAL);
|
||||
if (dma_mapping_error(&vpdev->dev, vdev->used[index])) {
|
||||
err = -ENOMEM;
|
||||
dev_err(_vop_dev(vdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, err);
|
||||
goto free_used;
|
||||
}
|
||||
writeq(vdev->used[index], &vqconfig->used_address);
|
||||
/*
|
||||
* To reassign the used ring here we are directly accessing
|
||||
* struct vring_virtqueue which is a private data structure
|
||||
* in virtio_ring.c. At the minimum, a BUILD_BUG_ON() in
|
||||
* vring_new_virtqueue() would ensure that
|
||||
* (&vq->vring == (struct vring *) (&vq->vq + 1));
|
||||
*/
|
||||
vr = (struct vring *)(vq + 1);
|
||||
vr->used = used;
|
||||
|
||||
vq->priv = vdev;
|
||||
return vq;
|
||||
free_used:
|
||||
free_pages((unsigned long)used,
|
||||
get_order(vdev->used_size[index]));
|
||||
del_vq:
|
||||
vring_del_virtqueue(vq);
|
||||
unmap:
|
||||
vpdev->hw_ops->iounmap(vpdev, vdev->vr[index]);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
static int vop_find_vqs(struct virtio_device *dev, unsigned nvqs,
|
||||
struct virtqueue *vqs[],
|
||||
vq_callback_t *callbacks[],
|
||||
const char * const names[])
|
||||
{
|
||||
struct _vop_vdev *vdev = to_vopvdev(dev);
|
||||
struct vop_device *vpdev = vdev->vpdev;
|
||||
struct mic_device_ctrl __iomem *dc = vdev->dc;
|
||||
int i, err, retry;
|
||||
|
||||
/* We must have this many virtqueues. */
|
||||
if (nvqs > ioread8(&vdev->desc->num_vq))
|
||||
return -ENOENT;
|
||||
|
||||
for (i = 0; i < nvqs; ++i) {
|
||||
dev_dbg(_vop_dev(vdev), "%s: %d: %s\n",
|
||||
__func__, i, names[i]);
|
||||
vqs[i] = vop_find_vq(dev, i, callbacks[i], names[i]);
|
||||
if (IS_ERR(vqs[i])) {
|
||||
err = PTR_ERR(vqs[i]);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
iowrite8(1, &dc->used_address_updated);
|
||||
/*
|
||||
* Send an interrupt to the host to inform it that used
|
||||
* rings have been re-assigned.
|
||||
*/
|
||||
vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db);
|
||||
for (retry = 100; --retry;) {
|
||||
if (!ioread8(&dc->used_address_updated))
|
||||
break;
|
||||
msleep(100);
|
||||
};
|
||||
|
||||
dev_dbg(_vop_dev(vdev), "%s: retry: %d\n", __func__, retry);
|
||||
if (!retry) {
|
||||
err = -ENODEV;
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
vop_del_vqs(dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* The config ops structure as defined by virtio config
|
||||
*/
|
||||
static struct virtio_config_ops vop_vq_config_ops = {
|
||||
.get_features = vop_get_features,
|
||||
.finalize_features = vop_finalize_features,
|
||||
.get = vop_get,
|
||||
.set = vop_set,
|
||||
.get_status = vop_get_status,
|
||||
.set_status = vop_set_status,
|
||||
.reset = vop_reset,
|
||||
.find_vqs = vop_find_vqs,
|
||||
.del_vqs = vop_del_vqs,
|
||||
};
|
||||
|
||||
static irqreturn_t vop_virtio_intr_handler(int irq, void *data)
|
||||
{
|
||||
struct _vop_vdev *vdev = data;
|
||||
struct vop_device *vpdev = vdev->vpdev;
|
||||
struct virtqueue *vq;
|
||||
|
||||
vpdev->hw_ops->ack_interrupt(vpdev, vdev->h2c_vdev_db);
|
||||
list_for_each_entry(vq, &vdev->vdev.vqs, list)
|
||||
vring_interrupt(0, vq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void vop_virtio_release_dev(struct device *_d)
|
||||
{
|
||||
/*
|
||||
* No need for a release method similar to virtio PCI.
|
||||
* Provide an empty one to avoid getting a warning from core.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* adds a new device and register it with virtio
|
||||
* appropriate drivers are loaded by the device model
|
||||
*/
|
||||
static int _vop_add_device(struct mic_device_desc __iomem *d,
|
||||
unsigned int offset, struct vop_device *vpdev,
|
||||
int dnode)
|
||||
{
|
||||
struct _vop_vdev *vdev;
|
||||
int ret;
|
||||
u8 type = ioread8(&d->type);
|
||||
|
||||
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
|
||||
if (!vdev)
|
||||
return -ENOMEM;
|
||||
|
||||
vdev->vpdev = vpdev;
|
||||
vdev->vdev.dev.parent = &vpdev->dev;
|
||||
vdev->vdev.dev.release = vop_virtio_release_dev;
|
||||
vdev->vdev.id.device = type;
|
||||
vdev->vdev.config = &vop_vq_config_ops;
|
||||
vdev->desc = d;
|
||||
vdev->dc = (void __iomem *)d + _vop_aligned_desc_size(d);
|
||||
vdev->dnode = dnode;
|
||||
vdev->vdev.priv = (void *)(u64)dnode;
|
||||
init_completion(&vdev->reset_done);
|
||||
|
||||
vdev->h2c_vdev_db = vpdev->hw_ops->next_db(vpdev);
|
||||
vdev->virtio_cookie = vpdev->hw_ops->request_irq(vpdev,
|
||||
vop_virtio_intr_handler, "virtio intr",
|
||||
vdev, vdev->h2c_vdev_db);
|
||||
if (IS_ERR(vdev->virtio_cookie)) {
|
||||
ret = PTR_ERR(vdev->virtio_cookie);
|
||||
goto kfree;
|
||||
}
|
||||
iowrite8((u8)vdev->h2c_vdev_db, &vdev->dc->h2c_vdev_db);
|
||||
vdev->c2h_vdev_db = ioread8(&vdev->dc->c2h_vdev_db);
|
||||
|
||||
ret = register_virtio_device(&vdev->vdev);
|
||||
if (ret) {
|
||||
dev_err(_vop_dev(vdev),
|
||||
"Failed to register vop device %u type %u\n",
|
||||
offset, type);
|
||||
goto free_irq;
|
||||
}
|
||||
writeq((u64)vdev, &vdev->dc->vdev);
|
||||
dev_dbg(_vop_dev(vdev), "%s: registered vop device %u type %u vdev %p\n",
|
||||
__func__, offset, type, vdev);
|
||||
|
||||
return 0;
|
||||
|
||||
free_irq:
|
||||
vpdev->hw_ops->free_irq(vpdev, vdev->virtio_cookie, vdev);
|
||||
kfree:
|
||||
kfree(vdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* match for a vop device with a specific desc pointer
|
||||
*/
|
||||
static int vop_match_desc(struct device *dev, void *data)
|
||||
{
|
||||
struct virtio_device *_dev = dev_to_virtio(dev);
|
||||
struct _vop_vdev *vdev = to_vopvdev(_dev);
|
||||
|
||||
return vdev->desc == (void __iomem *)data;
|
||||
}
|
||||
|
||||
static void _vop_handle_config_change(struct mic_device_desc __iomem *d,
|
||||
unsigned int offset,
|
||||
struct vop_device *vpdev)
|
||||
{
|
||||
struct mic_device_ctrl __iomem *dc
|
||||
= (void __iomem *)d + _vop_aligned_desc_size(d);
|
||||
struct _vop_vdev *vdev = (struct _vop_vdev *)readq(&dc->vdev);
|
||||
|
||||
if (ioread8(&dc->config_change) != MIC_VIRTIO_PARAM_CONFIG_CHANGED)
|
||||
return;
|
||||
|
||||
dev_dbg(&vpdev->dev, "%s %d\n", __func__, __LINE__);
|
||||
virtio_config_changed(&vdev->vdev);
|
||||
iowrite8(1, &dc->guest_ack);
|
||||
}
|
||||
|
||||
/*
|
||||
* removes a virtio device if a hot remove event has been
|
||||
* requested by the host.
|
||||
*/
|
||||
static int _vop_remove_device(struct mic_device_desc __iomem *d,
|
||||
unsigned int offset, struct vop_device *vpdev)
|
||||
{
|
||||
struct mic_device_ctrl __iomem *dc
|
||||
= (void __iomem *)d + _vop_aligned_desc_size(d);
|
||||
struct _vop_vdev *vdev = (struct _vop_vdev *)readq(&dc->vdev);
|
||||
u8 status;
|
||||
int ret = -1;
|
||||
|
||||
if (ioread8(&dc->config_change) == MIC_VIRTIO_PARAM_DEV_REMOVE) {
|
||||
dev_dbg(&vpdev->dev,
|
||||
"%s %d config_change %d type %d vdev %p\n",
|
||||
__func__, __LINE__,
|
||||
ioread8(&dc->config_change), ioread8(&d->type), vdev);
|
||||
status = ioread8(&d->status);
|
||||
reinit_completion(&vdev->reset_done);
|
||||
unregister_virtio_device(&vdev->vdev);
|
||||
vpdev->hw_ops->free_irq(vpdev, vdev->virtio_cookie, vdev);
|
||||
iowrite8(-1, &dc->h2c_vdev_db);
|
||||
if (status & VIRTIO_CONFIG_S_DRIVER_OK)
|
||||
wait_for_completion(&vdev->reset_done);
|
||||
kfree(vdev);
|
||||
iowrite8(1, &dc->guest_ack);
|
||||
dev_dbg(&vpdev->dev, "%s %d guest_ack %d\n",
|
||||
__func__, __LINE__, ioread8(&dc->guest_ack));
|
||||
iowrite8(-1, &d->type);
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define REMOVE_DEVICES true
|
||||
|
||||
static void _vop_scan_devices(void __iomem *dp, struct vop_device *vpdev,
|
||||
bool remove, int dnode)
|
||||
{
|
||||
s8 type;
|
||||
unsigned int i;
|
||||
struct mic_device_desc __iomem *d;
|
||||
struct mic_device_ctrl __iomem *dc;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
for (i = sizeof(struct mic_bootparam);
|
||||
i < MIC_DP_SIZE; i += _vop_total_desc_size(d)) {
|
||||
d = dp + i;
|
||||
dc = (void __iomem *)d + _vop_aligned_desc_size(d);
|
||||
/*
|
||||
* This read barrier is paired with the corresponding write
|
||||
* barrier on the host which is inserted before adding or
|
||||
* removing a virtio device descriptor, by updating the type.
|
||||
*/
|
||||
rmb();
|
||||
type = ioread8(&d->type);
|
||||
|
||||
/* end of list */
|
||||
if (type == 0)
|
||||
break;
|
||||
|
||||
if (type == -1)
|
||||
continue;
|
||||
|
||||
/* device already exists */
|
||||
dev = device_find_child(&vpdev->dev, (void __force *)d,
|
||||
vop_match_desc);
|
||||
if (dev) {
|
||||
if (remove)
|
||||
iowrite8(MIC_VIRTIO_PARAM_DEV_REMOVE,
|
||||
&dc->config_change);
|
||||
put_device(dev);
|
||||
_vop_handle_config_change(d, i, vpdev);
|
||||
ret = _vop_remove_device(d, i, vpdev);
|
||||
if (remove) {
|
||||
iowrite8(0, &dc->config_change);
|
||||
iowrite8(0, &dc->guest_ack);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* new device */
|
||||
dev_dbg(&vpdev->dev, "%s %d Adding new virtio device %p\n",
|
||||
__func__, __LINE__, d);
|
||||
if (!remove)
|
||||
_vop_add_device(d, i, vpdev, dnode);
|
||||
}
|
||||
}
|
||||
|
||||
static void vop_scan_devices(struct vop_info *vi,
|
||||
struct vop_device *vpdev, bool remove)
|
||||
{
|
||||
void __iomem *dp = vpdev->hw_ops->get_remote_dp(vpdev);
|
||||
|
||||
if (!dp)
|
||||
return;
|
||||
mutex_lock(&vi->vop_mutex);
|
||||
_vop_scan_devices(dp, vpdev, remove, vpdev->dnode);
|
||||
mutex_unlock(&vi->vop_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* vop_hotplug_device tries to find changes in the device page.
|
||||
*/
|
||||
static void vop_hotplug_devices(struct work_struct *work)
|
||||
{
|
||||
struct vop_info *vi = container_of(work, struct vop_info,
|
||||
hotplug_work);
|
||||
|
||||
vop_scan_devices(vi, vi->vpdev, !REMOVE_DEVICES);
|
||||
}
|
||||
|
||||
/*
|
||||
* Interrupt handler for hot plug/config changes etc.
|
||||
*/
|
||||
static irqreturn_t vop_extint_handler(int irq, void *data)
|
||||
{
|
||||
struct vop_info *vi = data;
|
||||
struct mic_bootparam __iomem *bp;
|
||||
struct vop_device *vpdev = vi->vpdev;
|
||||
|
||||
bp = vpdev->hw_ops->get_remote_dp(vpdev);
|
||||
dev_dbg(&vpdev->dev, "%s %d hotplug work\n",
|
||||
__func__, __LINE__);
|
||||
vpdev->hw_ops->ack_interrupt(vpdev, ioread8(&bp->h2c_config_db));
|
||||
schedule_work(&vi->hotplug_work);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int vop_driver_probe(struct vop_device *vpdev)
|
||||
{
|
||||
struct vop_info *vi;
|
||||
int rc;
|
||||
|
||||
vi = kzalloc(sizeof(*vi), GFP_KERNEL);
|
||||
if (!vi) {
|
||||
rc = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
dev_set_drvdata(&vpdev->dev, vi);
|
||||
vi->vpdev = vpdev;
|
||||
|
||||
mutex_init(&vi->vop_mutex);
|
||||
INIT_WORK(&vi->hotplug_work, vop_hotplug_devices);
|
||||
if (vpdev->dnode) {
|
||||
rc = vop_host_init(vi);
|
||||
if (rc < 0)
|
||||
goto free;
|
||||
} else {
|
||||
struct mic_bootparam __iomem *bootparam;
|
||||
|
||||
vop_scan_devices(vi, vpdev, !REMOVE_DEVICES);
|
||||
|
||||
vi->h2c_config_db = vpdev->hw_ops->next_db(vpdev);
|
||||
vi->cookie = vpdev->hw_ops->request_irq(vpdev,
|
||||
vop_extint_handler,
|
||||
"virtio_config_intr",
|
||||
vi, vi->h2c_config_db);
|
||||
if (IS_ERR(vi->cookie)) {
|
||||
rc = PTR_ERR(vi->cookie);
|
||||
goto free;
|
||||
}
|
||||
bootparam = vpdev->hw_ops->get_remote_dp(vpdev);
|
||||
iowrite8(vi->h2c_config_db, &bootparam->h2c_config_db);
|
||||
}
|
||||
vop_init_debugfs(vi);
|
||||
return 0;
|
||||
free:
|
||||
kfree(vi);
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void vop_driver_remove(struct vop_device *vpdev)
|
||||
{
|
||||
struct vop_info *vi = dev_get_drvdata(&vpdev->dev);
|
||||
|
||||
if (vpdev->dnode) {
|
||||
vop_host_uninit(vi);
|
||||
} else {
|
||||
struct mic_bootparam __iomem *bootparam =
|
||||
vpdev->hw_ops->get_remote_dp(vpdev);
|
||||
if (bootparam)
|
||||
iowrite8(-1, &bootparam->h2c_config_db);
|
||||
vpdev->hw_ops->free_irq(vpdev, vi->cookie, vi);
|
||||
flush_work(&vi->hotplug_work);
|
||||
vop_scan_devices(vi, vpdev, REMOVE_DEVICES);
|
||||
}
|
||||
vop_exit_debugfs(vi);
|
||||
kfree(vi);
|
||||
}
|
||||
|
||||
static struct vop_device_id id_table[] = {
|
||||
{ VOP_DEV_TRNSP, VOP_DEV_ANY_ID },
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
static struct vop_driver vop_driver = {
|
||||
.driver.name = KBUILD_MODNAME,
|
||||
.driver.owner = THIS_MODULE,
|
||||
.id_table = id_table,
|
||||
.probe = vop_driver_probe,
|
||||
.remove = vop_driver_remove,
|
||||
};
|
||||
|
||||
module_vop_driver(vop_driver);
|
||||
|
||||
MODULE_DEVICE_TABLE(mbus, id_table);
|
||||
MODULE_AUTHOR("Intel Corporation");
|
||||
MODULE_DESCRIPTION("Intel(R) Virtio Over PCIe (VOP) driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Intel MIC Platform Software Stack (MPSS)
|
||||
*
|
||||
* Copyright(c) 2013 Intel Corporation.
|
||||
* Copyright(c) 2016 Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
@@ -15,14 +15,21 @@
|
||||
* The full GNU General Public License is included in this distribution in
|
||||
* the file called "COPYING".
|
||||
*
|
||||
* Intel MIC Host driver.
|
||||
* Intel Virtio Over PCIe (VOP) driver.
|
||||
*
|
||||
*/
|
||||
#ifndef MIC_VIRTIO_H
|
||||
#define MIC_VIRTIO_H
|
||||
#ifndef _VOP_MAIN_H_
|
||||
#define _VOP_MAIN_H_
|
||||
|
||||
#include <linux/vringh.h>
|
||||
#include <linux/virtio_config.h>
|
||||
#include <linux/mic_ioctl.h>
|
||||
#include <linux/virtio.h>
|
||||
#include <linux/miscdevice.h>
|
||||
|
||||
#include <linux/mic_common.h>
|
||||
#include "../common/mic_dev.h"
|
||||
|
||||
#include "../bus/vop_bus.h"
|
||||
|
||||
/*
|
||||
* Note on endianness.
|
||||
@@ -39,38 +46,68 @@
|
||||
* in guest endianness.
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct mic_vringh - Virtio ring host information.
|
||||
/*
|
||||
* vop_info - Allocated per invocation of VOP probe
|
||||
*
|
||||
* @vring: The MIC vring used for setting up user space mappings.
|
||||
* @vpdev: VOP device
|
||||
* @hotplug_work: Handle virtio device creation, deletion and configuration
|
||||
* @cookie: Cookie received upon requesting a virtio configuration interrupt
|
||||
* @h2c_config_db: The doorbell used by the peer to indicate a config change
|
||||
* @vdev_list: List of "active" virtio devices injected in the peer node
|
||||
* @vop_mutex: Synchronize access to the device page as well as serialize
|
||||
* creation/deletion of virtio devices on the peer node
|
||||
* @dp: Peer device page information
|
||||
* @dbg: Debugfs entry
|
||||
* @dma_ch: The DMA channel used by this transport for data transfers.
|
||||
* @name: Name for this transport used in misc device creation.
|
||||
* @miscdev: The misc device registered.
|
||||
*/
|
||||
struct vop_info {
|
||||
struct vop_device *vpdev;
|
||||
struct work_struct hotplug_work;
|
||||
struct mic_irq *cookie;
|
||||
int h2c_config_db;
|
||||
struct list_head vdev_list;
|
||||
struct mutex vop_mutex;
|
||||
void __iomem *dp;
|
||||
struct dentry *dbg;
|
||||
struct dma_chan *dma_ch;
|
||||
char name[16];
|
||||
struct miscdevice miscdev;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct vop_vringh - Virtio ring host information.
|
||||
*
|
||||
* @vring: The VOP vring used for setting up user space mappings.
|
||||
* @vrh: The host VRINGH used for accessing the card vrings.
|
||||
* @riov: The VRINGH read kernel IOV.
|
||||
* @wiov: The VRINGH write kernel IOV.
|
||||
* @head: The VRINGH head index address passed to vringh_getdesc_kern(..).
|
||||
* @vr_mutex: Mutex for synchronizing access to the VRING.
|
||||
* @buf: Temporary kernel buffer used to copy in/out data
|
||||
* from/to the card via DMA.
|
||||
* @buf_da: dma address of buf.
|
||||
* @mvdev: Back pointer to MIC virtio device for vringh_notify(..).
|
||||
* @head: The VRINGH head index address passed to vringh_getdesc_kern(..).
|
||||
* @vdev: Back pointer to VOP virtio device for vringh_notify(..).
|
||||
*/
|
||||
struct mic_vringh {
|
||||
struct vop_vringh {
|
||||
struct mic_vring vring;
|
||||
struct vringh vrh;
|
||||
struct vringh_kiov riov;
|
||||
struct vringh_kiov wiov;
|
||||
u16 head;
|
||||
struct mutex vr_mutex;
|
||||
void *buf;
|
||||
dma_addr_t buf_da;
|
||||
struct mic_vdev *mvdev;
|
||||
u16 head;
|
||||
struct vop_vdev *vdev;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_vdev - Host information for a card Virtio device.
|
||||
* struct vop_vdev - Host information for a card Virtio device.
|
||||
*
|
||||
* @virtio_id - Virtio device id.
|
||||
* @waitq - Waitqueue to allow ring3 apps to poll.
|
||||
* @mdev - Back pointer to host MIC device.
|
||||
* @vpdev - pointer to VOP bus device.
|
||||
* @poll_wake - Used for waking up threads blocked in poll.
|
||||
* @out_bytes - Debug stats for number of bytes copied from host to card.
|
||||
* @in_bytes - Debug stats for number of bytes copied from card to host.
|
||||
@@ -82,18 +119,23 @@ struct mic_vringh {
|
||||
* the transfer length did not have the required DMA alignment.
|
||||
* @tx_dst_unaligned - Debug stats for number of bytes copied where the
|
||||
* destination address on the card did not have the required DMA alignment.
|
||||
* @mvr - Store per VRING data structures.
|
||||
* @vvr - Store per VRING data structures.
|
||||
* @virtio_bh_work - Work struct used to schedule virtio bottom half handling.
|
||||
* @dd - Virtio device descriptor.
|
||||
* @dc - Virtio device control fields.
|
||||
* @list - List of Virtio devices.
|
||||
* @virtio_db - The doorbell used by the card to interrupt the host.
|
||||
* @virtio_cookie - The cookie returned while requesting interrupts.
|
||||
* @vi: Transport information.
|
||||
* @vdev_mutex: Mutex synchronizing virtio device injection,
|
||||
* removal and data transfers.
|
||||
* @destroy: Track if a virtio device is being destroyed.
|
||||
* @deleted: The virtio device has been deleted.
|
||||
*/
|
||||
struct mic_vdev {
|
||||
struct vop_vdev {
|
||||
int virtio_id;
|
||||
wait_queue_head_t waitq;
|
||||
struct mic_device *mdev;
|
||||
struct vop_device *vpdev;
|
||||
int poll_wake;
|
||||
unsigned long out_bytes;
|
||||
unsigned long in_bytes;
|
||||
@@ -101,55 +143,28 @@ struct mic_vdev {
|
||||
unsigned long in_bytes_dma;
|
||||
unsigned long tx_len_unaligned;
|
||||
unsigned long tx_dst_unaligned;
|
||||
struct mic_vringh mvr[MIC_MAX_VRINGS];
|
||||
unsigned long rx_dst_unaligned;
|
||||
struct vop_vringh vvr[MIC_MAX_VRINGS];
|
||||
struct work_struct virtio_bh_work;
|
||||
struct mic_device_desc *dd;
|
||||
struct mic_device_ctrl *dc;
|
||||
struct list_head list;
|
||||
int virtio_db;
|
||||
struct mic_irq *virtio_cookie;
|
||||
struct vop_info *vi;
|
||||
struct mutex vdev_mutex;
|
||||
struct completion destroy;
|
||||
bool deleted;
|
||||
};
|
||||
|
||||
void mic_virtio_uninit(struct mic_device *mdev);
|
||||
int mic_virtio_add_device(struct mic_vdev *mvdev,
|
||||
void __user *argp);
|
||||
void mic_virtio_del_device(struct mic_vdev *mvdev);
|
||||
int mic_virtio_config_change(struct mic_vdev *mvdev,
|
||||
void __user *argp);
|
||||
int mic_virtio_copy_desc(struct mic_vdev *mvdev,
|
||||
struct mic_copy_desc *request);
|
||||
void mic_virtio_reset_devices(struct mic_device *mdev);
|
||||
void mic_bh_handler(struct work_struct *work);
|
||||
|
||||
/* Helper API to obtain the MIC PCIe device */
|
||||
static inline struct device *mic_dev(struct mic_vdev *mvdev)
|
||||
{
|
||||
return &mvdev->mdev->pdev->dev;
|
||||
}
|
||||
|
||||
/* Helper API to check if a virtio device is initialized */
|
||||
static inline int mic_vdev_inited(struct mic_vdev *mvdev)
|
||||
{
|
||||
/* Device has not been created yet */
|
||||
if (!mvdev->dd || !mvdev->dd->type) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Device has been removed/deleted */
|
||||
if (mvdev->dd->type == -1) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -ENODEV);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper API to check if a virtio device is running */
|
||||
static inline bool mic_vdevup(struct mic_vdev *mvdev)
|
||||
static inline bool vop_vdevup(struct vop_vdev *vdev)
|
||||
{
|
||||
return !!mvdev->dd->status;
|
||||
return !!vdev->dd->status;
|
||||
}
|
||||
|
||||
void vop_init_debugfs(struct vop_info *vi);
|
||||
void vop_exit_debugfs(struct vop_info *vi);
|
||||
int vop_host_init(struct vop_info *vi);
|
||||
void vop_host_uninit(struct vop_info *vi);
|
||||
#endif
|
1165
drivers/misc/mic/vop/vop_vringh.c
Normal file
1165
drivers/misc/mic/vop/vop_vringh.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -503,8 +503,7 @@ static ssize_t pch_phub_bin_read(struct file *filp, struct kobject *kobj,
|
||||
int err;
|
||||
ssize_t rom_size;
|
||||
|
||||
struct pch_phub_reg *chip =
|
||||
dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
struct pch_phub_reg *chip = dev_get_drvdata(kobj_to_dev(kobj));
|
||||
|
||||
ret = mutex_lock_interruptible(&pch_phub_mutex);
|
||||
if (ret) {
|
||||
@@ -514,8 +513,10 @@ static ssize_t pch_phub_bin_read(struct file *filp, struct kobject *kobj,
|
||||
|
||||
/* Get Rom signature */
|
||||
chip->pch_phub_extrom_base_address = pci_map_rom(chip->pdev, &rom_size);
|
||||
if (!chip->pch_phub_extrom_base_address)
|
||||
if (!chip->pch_phub_extrom_base_address) {
|
||||
err = -ENODATA;
|
||||
goto exrom_map_err;
|
||||
}
|
||||
|
||||
pch_phub_read_serial_rom(chip, chip->pch_opt_rom_start_address,
|
||||
(unsigned char *)&rom_signature);
|
||||
@@ -567,8 +568,7 @@ static ssize_t pch_phub_bin_write(struct file *filp, struct kobject *kobj,
|
||||
unsigned int addr_offset;
|
||||
int ret;
|
||||
ssize_t rom_size;
|
||||
struct pch_phub_reg *chip =
|
||||
dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
struct pch_phub_reg *chip = dev_get_drvdata(kobj_to_dev(kobj));
|
||||
|
||||
ret = mutex_lock_interruptible(&pch_phub_mutex);
|
||||
if (ret)
|
||||
|
@@ -632,7 +632,6 @@ long st_register(struct st_proto_s *new_proto)
|
||||
spin_unlock_irqrestore(&st_gdata->lock, flags);
|
||||
return err;
|
||||
}
|
||||
pr_debug("done %s(%d) ", __func__, new_proto->chnl_id);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(st_register);
|
||||
|
||||
|
@@ -113,5 +113,5 @@ module_exit(vmci_drv_exit);
|
||||
|
||||
MODULE_AUTHOR("VMware, Inc.");
|
||||
MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface.");
|
||||
MODULE_VERSION("1.1.3.0-k");
|
||||
MODULE_VERSION("1.1.4.0-k");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
Reference in New Issue
Block a user