diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 35 | ||||
-rw-r--r-- | drivers/misc/Makefile | 4 | ||||
-rw-r--r-- | drivers/misc/ab8500-pwm.c | 6 | ||||
-rw-r--r-- | drivers/misc/ad525x_dpot.c | 2 | ||||
-rw-r--r-- | drivers/misc/bh1780gli.c | 2 | ||||
-rw-r--r-- | drivers/misc/bmp085-i2c.c | 91 | ||||
-rw-r--r-- | drivers/misc/bmp085-spi.c | 91 | ||||
-rw-r--r-- | drivers/misc/bmp085.c | 356 | ||||
-rw-r--r-- | drivers/misc/bmp085.h | 33 | ||||
-rw-r--r-- | drivers/misc/c2port/Kconfig | 6 | ||||
-rw-r--r-- | drivers/misc/eeprom/at25.c | 19 | ||||
-rw-r--r-- | drivers/misc/max8997-muic.c | 495 | ||||
-rw-r--r-- | drivers/misc/mei/Kconfig | 28 | ||||
-rw-r--r-- | drivers/misc/mei/Makefile | 11 | ||||
-rw-r--r-- | drivers/misc/mei/hw.h | 332 | ||||
-rw-r--r-- | drivers/misc/mei/init.c | 735 | ||||
-rw-r--r-- | drivers/misc/mei/interface.c | 428 | ||||
-rw-r--r-- | drivers/misc/mei/interface.h | 75 | ||||
-rw-r--r-- | drivers/misc/mei/interrupt.c | 1590 | ||||
-rw-r--r-- | drivers/misc/mei/iorw.c | 590 | ||||
-rw-r--r-- | drivers/misc/mei/main.c | 1230 | ||||
-rw-r--r-- | drivers/misc/mei/mei_dev.h | 428 | ||||
-rw-r--r-- | drivers/misc/mei/wd.c | 379 | ||||
-rw-r--r-- | drivers/misc/pch_phub.c | 4 | ||||
-rw-r--r-- | drivers/misc/pti.c | 2 |
25 files changed, 6276 insertions, 696 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c7795096d43b..2661f6e366f9 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -452,14 +452,32 @@ config ARM_CHARLCD still useful. config BMP085 - tristate "BMP085 digital pressure sensor" + bool + depends on SYSFS + +config BMP085_I2C + tristate "BMP085 digital pressure sensor on I2C" + select BMP085 + select REGMAP_I2C depends on I2C && SYSFS help - If you say yes here you get support for the Bosch Sensortec - BMP085 digital pressure sensor. + Say Y here if you want to support Bosch Sensortec's digital pressure + sensor hooked to an I2C bus. To compile this driver as a module, choose M here: the - module will be called bmp085. + module will be called bmp085-i2c. + +config BMP085_SPI + tristate "BMP085 digital pressure sensor on SPI" + select BMP085 + select REGMAP_SPI + depends on SPI_MASTER && SYSFS + help + Say Y here if you want to support Bosch Sensortec's digital pressure + sensor hooked to an SPI bus. + + To compile this driver as a module, choose M here: the + module will be called bmp085-spi. config PCH_PHUB tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB" @@ -490,14 +508,6 @@ config USB_SWITCH_FSA9480 stereo and mono audio, video, microphone and UART data to use a common connector port. -config MAX8997_MUIC - tristate "MAX8997 MUIC Support" - depends on MFD_MAX8997 - help - If you say yes here you get support for the MUIC device of - Maxim MAX8997 PMIC. - The MAX8997 MUIC is a USB port accessory detector and switch. - source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" @@ -506,4 +516,5 @@ source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" source "drivers/misc/altera-stapl/Kconfig" +source "drivers/misc/mei/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 3e1d80106f04..456972faaeb3 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o obj-$(CONFIG_BMP085) += bmp085.o +obj-$(CONFIG_BMP085_I2C) += bmp085-i2c.o +obj-$(CONFIG_BMP085_SPI) += bmp085-spi.o obj-$(CONFIG_ICS932S401) += ics932s401.o obj-$(CONFIG_LKDTM) += lkdtm.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o @@ -48,4 +50,4 @@ obj-y += lis3lv02d/ obj-y += carma/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ -obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o +obj-$(CONFIG_INTEL_MEI) += mei/ diff --git a/drivers/misc/ab8500-pwm.c b/drivers/misc/ab8500-pwm.c index d7a9aa14e5d5..042a8fe4efaa 100644 --- a/drivers/misc/ab8500-pwm.c +++ b/drivers/misc/ab8500-pwm.c @@ -142,10 +142,16 @@ static int __devexit ab8500_pwm_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id ab8500_pwm_match[] = { + { .compatible = "stericsson,ab8500-pwm", }, + {} +}; + static struct platform_driver ab8500_pwm_driver = { .driver = { .name = "ab8500-pwm", .owner = THIS_MODULE, + .of_match_table = ab8500_pwm_match, }, .probe = ab8500_pwm_probe, .remove = __devexit_p(ab8500_pwm_remove), diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c index 1d1d42615915..6938f1be664d 100644 --- a/drivers/misc/ad525x_dpot.c +++ b/drivers/misc/ad525x_dpot.c @@ -749,7 +749,7 @@ exit: } EXPORT_SYMBOL(ad_dpot_probe); -__devexit int ad_dpot_remove(struct device *dev) +int ad_dpot_remove(struct device *dev) { struct dpot_data *data = dev_get_drvdata(dev); int i; diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 54f6f39f990a..f1f9877f3fdf 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -248,7 +248,7 @@ static const struct i2c_device_id bh1780_id[] = { static struct i2c_driver bh1780_driver = { .probe = bh1780_probe, - .remove = bh1780_remove, + .remove = __devexit_p(bh1780_remove), .id_table = bh1780_id, .driver = { .name = "bh1780", diff --git a/drivers/misc/bmp085-i2c.c b/drivers/misc/bmp085-i2c.c new file mode 100644 index 000000000000..9943971c13e3 --- /dev/null +++ b/drivers/misc/bmp085-i2c.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2012 Bosch Sensortec GmbH + * Copyright (c) 2012 Unixphere AB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include "bmp085.h" + +#define BMP085_I2C_ADDRESS 0x77 + +static const unsigned short normal_i2c[] = { BMP085_I2C_ADDRESS, + I2C_CLIENT_END }; + +static int bmp085_i2c_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + if (client->addr != BMP085_I2C_ADDRESS) + return -ENODEV; + + return bmp085_detect(&client->dev); +} + +static int __devinit bmp085_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct regmap *regmap = devm_regmap_init_i2c(client, + &bmp085_regmap_config); + + if (IS_ERR(regmap)) { + err = PTR_ERR(regmap); + dev_err(&client->dev, "Failed to init regmap: %d\n", err); + return err; + } + + return bmp085_probe(&client->dev, regmap); +} + +static int bmp085_i2c_remove(struct i2c_client *client) +{ + return bmp085_remove(&client->dev); +} + +static const struct of_device_id bmp085_of_match[] = { + { .compatible = "bosch,bmp085", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bmp085_of_match); + +static const struct i2c_device_id bmp085_id[] = { + { BMP085_NAME, 0 }, + { "bmp180", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmp085_id); + +static struct i2c_driver bmp085_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = BMP085_NAME, + .of_match_table = bmp085_of_match + }, + .id_table = bmp085_id, + .probe = bmp085_i2c_probe, + .remove = __devexit_p(bmp085_i2c_remove), + + .detect = bmp085_i2c_detect, + .address_list = normal_i2c +}; + +module_i2c_driver(bmp085_i2c_driver); + +MODULE_AUTHOR("Eric Andersson <eric.andersson@unixphere.com>"); +MODULE_DESCRIPTION("BMP085 I2C bus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/bmp085-spi.c b/drivers/misc/bmp085-spi.c new file mode 100644 index 000000000000..78aaff9b5231 --- /dev/null +++ b/drivers/misc/bmp085-spi.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2012 Bosch Sensortec GmbH + * Copyright (c) 2012 Unixphere AB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include "bmp085.h" + +static int __devinit bmp085_spi_probe(struct spi_device *client) +{ + int err; + struct regmap *regmap; + + client->bits_per_word = 8; + err = spi_setup(client); + if (err < 0) { + dev_err(&client->dev, "spi_setup failed!\n"); + return err; + } + + regmap = devm_regmap_init_spi(client, &bmp085_regmap_config); + if (IS_ERR(regmap)) { + err = PTR_ERR(regmap); + dev_err(&client->dev, "Failed to init regmap: %d\n", err); + return err; + } + + return bmp085_probe(&client->dev, regmap); +} + +static int bmp085_spi_remove(struct spi_device *client) +{ + return bmp085_remove(&client->dev); +} + +static const struct of_device_id bmp085_of_match[] = { + { .compatible = "bosch,bmp085", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bmp085_of_match); + +static const struct spi_device_id bmp085_id[] = { + { "bmp180", 0 }, + { "bmp181", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmp085_id); + +static struct spi_driver bmp085_spi_driver = { + .driver = { + .owner = THIS_MODULE, + .name = BMP085_NAME, + .of_match_table = bmp085_of_match + }, + .id_table = bmp085_id, + .probe = bmp085_spi_probe, + .remove = __devexit_p(bmp085_spi_remove) +}; + +static int __init bmp085_spi_init(void) +{ + return spi_register_driver(&bmp085_spi_driver); +} + +static void __exit bmp085_spi_exit(void) +{ + spi_unregister_driver(&bmp085_spi_driver); +} + +MODULE_AUTHOR("Eric Andersson <eric.andersson@unixphere.com>"); +MODULE_DESCRIPTION("BMP085 SPI bus driver"); +MODULE_LICENSE("GPL"); + +module_init(bmp085_spi_init); +module_exit(bmp085_spi_exit); diff --git a/drivers/misc/bmp085.c b/drivers/misc/bmp085.c index 76c3064629f1..62e418293b7e 100644 --- a/drivers/misc/bmp085.c +++ b/drivers/misc/bmp085.c @@ -1,62 +1,62 @@ /* Copyright (c) 2010 Christoph Mair <christoph.mair@gmail.com> - - This driver supports the bmp085 digital barometric pressure - and temperature sensor from Bosch Sensortec. The datasheet - is available from their website: - http://www.bosch-sensortec.com/content/language1/downloads/BST-BMP085-DS000-05.pdf - - A pressure measurement is issued by reading from pressure0_input. - The return value ranges from 30000 to 110000 pascal with a resulution - of 1 pascal (0.01 millibar) which enables measurements from 9000m above - to 500m below sea level. - - The temperature can be read from temp0_input. Values range from - -400 to 850 representing the ambient temperature in degree celsius - multiplied by 10.The resolution is 0.1 celsius. - - Because ambient pressure is temperature dependent, a temperature - measurement will be executed automatically even if the user is reading - from pressure0_input. This happens if the last temperature measurement - has been executed more then one second ago. - - To decrease RMS noise from pressure measurements, the bmp085 can - autonomously calculate the average of up to eight samples. This is - set up by writing to the oversampling sysfs file. Accepted values - are 0, 1, 2 and 3. 2^x when x is the value written to this file - specifies the number of samples used to calculate the ambient pressure. - RMS noise is specified with six pascal (without averaging) and decreases - down to 3 pascal when using an oversampling setting of 3. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - + * Copyright (c) 2012 Bosch Sensortec GmbH + * Copyright (c) 2012 Unixphere AB + * + * This driver supports the bmp085 and bmp18x digital barometric pressure + * and temperature sensors from Bosch Sensortec. The datasheets + * are available from their website: + * http://www.bosch-sensortec.com/content/language1/downloads/BST-BMP085-DS000-05.pdf + * http://www.bosch-sensortec.com/content/language1/downloads/BST-BMP180-DS000-07.pdf + * + * A pressure measurement is issued by reading from pressure0_input. + * The return value ranges from 30000 to 110000 pascal with a resulution + * of 1 pascal (0.01 millibar) which enables measurements from 9000m above + * to 500m below sea level. + * + * The temperature can be read from temp0_input. Values range from + * -400 to 850 representing the ambient temperature in degree celsius + * multiplied by 10.The resolution is 0.1 celsius. + * + * Because ambient pressure is temperature dependent, a temperature + * measurement will be executed automatically even if the user is reading + * from pressure0_input. This happens if the last temperature measurement + * has been executed more then one second ago. + * + * To decrease RMS noise from pressure measurements, the bmp085 can + * autonomously calculate the average of up to eight samples. This is + * set up by writing to the oversampling sysfs file. Accepted values + * are 0, 1, 2 and 3. 2^x when x is the value written to this file + * specifies the number of samples used to calculate the ambient pressure. + * RMS noise is specified with six pascal (without averaging) and decreases + * down to 3 pascal when using an oversampling setting of 3. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ #include <linux/module.h> +#include <linux/device.h> #include <linux/init.h> -#include <linux/i2c.h> #include <linux/slab.h> #include <linux/delay.h> +#include <linux/of.h> +#include "bmp085.h" - -#define BMP085_I2C_ADDRESS 0x77 #define BMP085_CHIP_ID 0x55 - #define BMP085_CALIBRATION_DATA_START 0xAA #define BMP085_CALIBRATION_DATA_LENGTH 11 /* 16 bit values */ #define BMP085_CHIP_ID_REG 0xD0 -#define BMP085_VERSION_REG 0xD1 #define BMP085_CTRL_REG 0xF4 #define BMP085_TEMP_MEASUREMENT 0x2E #define BMP085_PRESSURE_MEASUREMENT 0x34 @@ -65,12 +65,6 @@ #define BMP085_CONVERSION_REGISTER_XLSB 0xF8 #define BMP085_TEMP_CONVERSION_TIME 5 -#define BMP085_CLIENT_NAME "bmp085" - - -static const unsigned short normal_i2c[] = { BMP085_I2C_ADDRESS, - I2C_CLIENT_END }; - struct bmp085_calibration_data { s16 AC1, AC2, AC3; u16 AC4, AC5, AC6; @@ -78,35 +72,30 @@ struct bmp085_calibration_data { s16 MB, MC, MD; }; - -/* Each client has this additional data */ struct bmp085_data { - struct i2c_client *client; - struct mutex lock; - struct bmp085_calibration_data calibration; - u32 raw_temperature; - u32 raw_pressure; - unsigned char oversampling_setting; + struct device *dev; + struct regmap *regmap; + struct mutex lock; + struct bmp085_calibration_data calibration; + u8 oversampling_setting; + u32 raw_temperature; + u32 raw_pressure; + u32 temp_measurement_period; unsigned long last_temp_measurement; - s32 b6; /* calculated temperature correction coefficient */ + u8 chip_id; + s32 b6; /* calculated temperature correction coefficient */ }; - -static s32 bmp085_read_calibration_data(struct i2c_client *client) +static s32 bmp085_read_calibration_data(struct bmp085_data *data) { u16 tmp[BMP085_CALIBRATION_DATA_LENGTH]; - struct bmp085_data *data = i2c_get_clientdata(client); struct bmp085_calibration_data *cali = &(data->calibration); - s32 status = i2c_smbus_read_i2c_block_data(client, - BMP085_CALIBRATION_DATA_START, - BMP085_CALIBRATION_DATA_LENGTH*sizeof(u16), - (u8 *)tmp); + s32 status = regmap_bulk_read(data->regmap, + BMP085_CALIBRATION_DATA_START, (u8 *)tmp, + (BMP085_CALIBRATION_DATA_LENGTH << 1)); if (status < 0) return status; - if (status != BMP085_CALIBRATION_DATA_LENGTH*sizeof(u16)) - return -EIO; - cali->AC1 = be16_to_cpu(tmp[0]); cali->AC2 = be16_to_cpu(tmp[1]); cali->AC3 = be16_to_cpu(tmp[2]); @@ -121,30 +110,26 @@ static s32 bmp085_read_calibration_data(struct i2c_client *client) return 0; } - static s32 bmp085_update_raw_temperature(struct bmp085_data *data) { u16 tmp; s32 status; mutex_lock(&data->lock); - status = i2c_smbus_write_byte_data(data->client, BMP085_CTRL_REG, - BMP085_TEMP_MEASUREMENT); - if (status != 0) { - dev_err(&data->client->dev, + status = regmap_write(data->regmap, BMP085_CTRL_REG, + BMP085_TEMP_MEASUREMENT); + if (status < 0) { + dev_err(data->dev, "Error while requesting temperature measurement.\n"); goto exit; } msleep(BMP085_TEMP_CONVERSION_TIME); - status = i2c_smbus_read_i2c_block_data(data->client, - BMP085_CONVERSION_REGISTER_MSB, sizeof(tmp), (u8 *)&tmp); - if (status < 0) - goto exit; - if (status != sizeof(tmp)) { - dev_err(&data->client->dev, + status = regmap_bulk_read(data->regmap, BMP085_CONVERSION_REGISTER_MSB, + &tmp, sizeof(tmp)); + if (status < 0) { + dev_err(data->dev, "Error while reading temperature measurement result\n"); - status = -EIO; goto exit; } data->raw_temperature = be16_to_cpu(tmp); @@ -162,10 +147,11 @@ static s32 bmp085_update_raw_pressure(struct bmp085_data *data) s32 status; mutex_lock(&data->lock); - status = i2c_smbus_write_byte_data(data->client, BMP085_CTRL_REG, - BMP085_PRESSURE_MEASUREMENT + (data->oversampling_setting<<6)); - if (status != 0) { - dev_err(&data->client->dev, + status = regmap_write(data->regmap, BMP085_CTRL_REG, + BMP085_PRESSURE_MEASUREMENT + + (data->oversampling_setting << 6)); + if (status < 0) { + dev_err(data->dev, "Error while requesting pressure measurement.\n"); goto exit; } @@ -174,14 +160,11 @@ static s32 bmp085_update_raw_pressure(struct bmp085_data *data) msleep(2+(3 << data->oversampling_setting)); /* copy data into a u32 (4 bytes), but skip the first byte. */ - status = i2c_smbus_read_i2c_block_data(data->client, - BMP085_CONVERSION_REGISTER_MSB, 3, ((u8 *)&tmp)+1); - if (status < 0) - goto exit; - if (status != 3) { - dev_err(&data->client->dev, + status = regmap_bulk_read(data->regmap, BMP085_CONVERSION_REGISTER_MSB, + ((u8 *)&tmp)+1, 3); + if (status < 0) { + dev_err(data->dev, "Error while reading pressure measurement results\n"); - status = -EIO; goto exit; } data->raw_pressure = be32_to_cpu((tmp)); @@ -193,7 +176,6 @@ exit: return status; } - /* * This function starts the temperature measurement and returns the value * in tenth of a degree celsius. @@ -205,7 +187,7 @@ static s32 bmp085_get_temperature(struct bmp085_data *data, int *temperature) int status; status = bmp085_update_raw_temperature(data); - if (status != 0) + if (status < 0) goto exit; x1 = ((data->raw_temperature - cali->AC6) * cali->AC5) >> 15; @@ -222,8 +204,10 @@ exit: /* * This function starts the pressure measurement and returns the value * in millibar. Since the pressure depends on the ambient temperature, - * a temperature measurement is executed if the last known value is older - * than one second. + * a temperature measurement is executed according to the given temperature + * measurement period (default is 1 sec boundary). This period could vary + * and needs to be adjusted according to the sensor environment, i.e. if big + * temperature variations then the temperature needs to be read out often. */ static s32 bmp085_get_pressure(struct bmp085_data *data, int *pressure) { @@ -234,16 +218,16 @@ static s32 bmp085_get_pressure(struct bmp085_data *data, int *pressure) int status; /* alt least every second force an update of the ambient temperature */ - if (data->last_temp_measurement == 0 || - time_is_before_jiffies(data->last_temp_measurement + 1*HZ)) { + if ((data->last_temp_measurement == 0) || + time_is_before_jiffies(data->last_temp_measurement + 1*HZ)) { status = bmp085_get_temperature(data, NULL); - if (status != 0) - goto exit; + if (status < 0) + return status; } status = bmp085_update_raw_pressure(data); - if (status != 0) - goto exit; + if (status < 0) + return status; x1 = (data->b6 * data->b6) >> 12; x1 *= cali->B2; @@ -274,15 +258,14 @@ static s32 bmp085_get_pressure(struct bmp085_data *data, int *pressure) *pressure = p; -exit: - return status; + return 0; } /* * This function sets the chip-internal oversampling. Valid values are 0..3. * The chip will use 2^oversampling samples for internal averaging. * This influences the measurement time and the accuracy; larger values - * increase both. The datasheet gives on overview on how measurement time, + * increase both. The datasheet gives an overview on how measurement time, * accuracy and noise correlate. */ static void bmp085_set_oversampling(struct bmp085_data *data, @@ -306,22 +289,25 @@ static ssize_t set_oversampling(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); - struct bmp085_data *data = i2c_get_clientdata(client); + struct bmp085_data *data = dev_get_drvdata(dev); unsigned long oversampling; - int success = strict_strtoul(buf, 10, &oversampling); - if (success == 0) { + int err = kstrtoul(buf, 10, &oversampling); + + if (err == 0) { + mutex_lock(&data->lock); bmp085_set_oversampling(data, oversampling); + mutex_unlock(&data->lock); return count; } - return success; + + return err; } static ssize_t show_oversampling(struct device *dev, struct device_attribute *attr, char *buf) { - struct i2c_client *client = to_i2c_client(dev); - struct bmp085_data *data = i2c_get_clientdata(client); + struct bmp085_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", bmp085_get_oversampling(data)); } static DEVICE_ATTR(oversampling, S_IWUSR | S_IRUGO, @@ -333,11 +319,10 @@ static ssize_t show_temperature(struct device *dev, { int temperature; int status; - struct i2c_client *client = to_i2c_client(dev); - struct bmp085_data *data = i2c_get_clientdata(client); + struct bmp085_data *data = dev_get_drvdata(dev); status = bmp085_get_temperature(data, &temperature); - if (status != 0) + if (status < 0) return status; else return sprintf(buf, "%d\n", temperature); @@ -350,11 +335,10 @@ static ssize_t show_pressure(struct device *dev, { int pressure; int status; - struct i2c_client *client = to_i2c_client(dev); - struct bmp085_data *data = i2c_get_clientdata(client); + struct bmp085_data *data = dev_get_drvdata(dev); status = bmp085_get_pressure(data, &pressure); - if (status != 0) + if (status < 0) return status; else return sprintf(buf, "%d\n", pressure); @@ -373,38 +357,70 @@ static const struct attribute_group bmp085_attr_group = { .attrs = bmp085_attributes, }; -static int bmp085_detect(struct i2c_client *client, struct i2c_board_info *info) +int bmp085_detect(struct device *dev) { - if (client->addr != BMP085_I2C_ADDRESS) - return -ENODEV; + struct bmp085_data *data = dev_get_drvdata(dev); + unsigned int id; + int ret; - if (i2c_smbus_read_byte_data(client, BMP085_CHIP_ID_REG) != BMP085_CHIP_ID) + ret = regmap_read(data->regmap, BMP085_CHIP_ID_REG, &id); + if (ret < 0) + return ret; + + if (id != data->chip_id) return -ENODEV; return 0; } +EXPORT_SYMBOL_GPL(bmp085_detect); -static int bmp085_init_client(struct i2c_client *client) +static void __init bmp085_get_of_properties(struct bmp085_data *data) { - unsigned char version; - int status; - struct bmp085_data *data = i2c_get_clientdata(client); - data->client = client; - status = bmp085_read_calibration_data(client); - if (status != 0) - goto exit; - version = i2c_smbus_read_byte_data(client, BMP085_VERSION_REG); +#ifdef CONFIG_OF + struct device_node *np = data->dev->of_node; + u32 prop; + + if (!np) + return; + + if (!of_property_read_u32(np, "chip-id", &prop)) + data->chip_id = prop & 0xff; + + if (!of_property_read_u32(np, "temp-measurement-period", &prop)) + data->temp_measurement_period = (prop/100)*HZ; + + if (!of_property_read_u32(np, "default-oversampling", &prop)) + data->oversampling_setting = prop & 0xff; +#endif +} + +static int bmp085_init_client(struct bmp085_data *data) +{ + int status = bmp085_read_calibration_data(data); + + if (status < 0) + return status; + + /* default settings */ + data->chip_id = BMP085_CHIP_ID; data->last_temp_measurement = 0; + data->temp_measurement_period = 1*HZ; data->oversampling_setting = 3; + + bmp085_get_of_properties(data); + mutex_init(&data->lock); - dev_info(&data->client->dev, "BMP085 ver. %d.%d found.\n", - (version & 0x0F), (version & 0xF0) >> 4); -exit: - return status; + + return 0; } -static int __devinit bmp085_probe(struct i2c_client *client, - const struct i2c_device_id *id) +struct regmap_config bmp085_regmap_config = { + .reg_bits = 8, + .val_bits = 8 +}; +EXPORT_SYMBOL_GPL(bmp085_regmap_config); + +__devinit int bmp085_probe(struct device *dev, struct regmap *regmap) { struct bmp085_data *data; int err = 0; @@ -415,58 +431,48 @@ static int __devinit bmp085_probe(struct i2c_client *client, goto exit; } - /* default settings after POR */ - data->oversampling_setting = 0x00; - - i2c_set_clientdata(client, data); + dev_set_drvdata(dev, data); + data->dev = dev; + data->regmap = regmap; /* Initialize the BMP085 chip */ - err = bmp085_init_client(client); - if (err != 0) + err = bmp085_init_client(data); + if (err < 0) goto exit_free; + err = bmp085_detect(dev); + if (err < 0) { + dev_err(dev, "%s: chip_id failed!\n", BMP085_NAME); + goto exit_free; + } + /* Register sysfs hooks */ - err = sysfs_create_group(&client->dev.kobj, &bmp085_attr_group); + err = sysfs_create_group(&dev->kobj, &bmp085_attr_group); if (err) goto exit_free; - dev_info(&data->client->dev, "Successfully initialized bmp085!\n"); - goto exit; + dev_info(dev, "Successfully initialized %s!\n", BMP085_NAME); + + return 0; exit_free: kfree(data); exit: return err; } +EXPORT_SYMBOL_GPL(bmp085_probe); -static int __devexit bmp085_remove(struct i2c_client *client) +int bmp085_remove(struct device *dev) { - sysfs_remove_group(&client->dev.kobj, &bmp085_attr_group); - kfree(i2c_get_clientdata(client)); - return 0; -} + struct bmp085_data *data = dev_get_drvdata(dev); -static const struct i2c_device_id bmp085_id[] = { - { "bmp085", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, bmp085_id); - -static struct i2c_driver bmp085_driver = { - .driver = { - .owner = THIS_MODULE, - .name = "bmp085" - }, - .id_table = bmp085_id, - .probe = bmp085_probe, - .remove = __devexit_p(bmp085_remove), - - .detect = bmp085_detect, - .address_list = normal_i2c -}; + sysfs_remove_group(&data->dev->kobj, &bmp085_attr_group); + kfree(data); -module_i2c_driver(bmp085_driver); + return 0; +} +EXPORT_SYMBOL_GPL(bmp085_remove); -MODULE_AUTHOR("Christoph Mair <christoph.mair@gmail.com"); +MODULE_AUTHOR("Christoph Mair <christoph.mair@gmail.com>"); MODULE_DESCRIPTION("BMP085 driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/misc/bmp085.h b/drivers/misc/bmp085.h new file mode 100644 index 000000000000..2b8f615bca92 --- /dev/null +++ b/drivers/misc/bmp085.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012 Bosch Sensortec GmbH + * Copyright (c) 2012 Unixphere AB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _BMP085_H +#define _BMP085_H + +#include <linux/regmap.h> + +#define BMP085_NAME "bmp085" + +extern struct regmap_config bmp085_regmap_config; + +int bmp085_probe(struct device *dev, struct regmap *regmap); +int bmp085_remove(struct device *dev); +int bmp085_detect(struct device *dev); + +#endif diff --git a/drivers/misc/c2port/Kconfig b/drivers/misc/c2port/Kconfig index e46af9a5810d..33ee834e1b83 100644 --- a/drivers/misc/c2port/Kconfig +++ b/drivers/misc/c2port/Kconfig @@ -5,7 +5,7 @@ menuconfig C2PORT tristate "Silicon Labs C2 port support (EXPERIMENTAL)" depends on EXPERIMENTAL - default no + default n help This option enables support for Silicon Labs C2 port used to program Silicon micro controller chips (and other 8051 compatible). @@ -23,8 +23,8 @@ if C2PORT config C2PORT_DURAMAR_2150 tristate "C2 port support for Eurotech's Duramar 2150 (EXPERIMENTAL)" - depends on X86 && C2PORT - default no + depends on X86 + default n help This option enables C2 support for the Eurotech's Duramar 2150 on board micro controller. diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 01ab3c9b4cf7..0842c2994ee2 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -50,6 +50,7 @@ struct at25_data { #define AT25_SR_BP1 0x08 #define AT25_SR_WPEN 0x80 /* writeprotect enable */ +#define AT25_INSTR_BIT3 0x08 /* Additional address bit in instr */ #define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */ @@ -75,6 +76,7 @@ at25_ee_read( ssize_t status; struct spi_transfer t[2]; struct spi_message m; + u8 instr; if (unlikely(offset >= at25->bin.size)) return 0; @@ -84,7 +86,12 @@ at25_ee_read( return count; cp = command; - *cp++ = AT25_READ; + + instr = AT25_READ; + if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR) + if (offset >= (1U << (at25->addrlen * 8))) + instr |= AT25_INSTR_BIT3; + *cp++ = instr; /* 8/16/24-bit address is written MSB first */ switch (at25->addrlen) { @@ -167,14 +174,14 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off, /* For write, rollover is within the page ... so we write at * most one page, then manually roll over to the next page. */ - bounce[0] = AT25_WRITE; mutex_lock(&at25->lock); do { unsigned long timeout, retries; unsigned segment; unsigned offset = (unsigned) off; - u8 *cp = bounce + 1; + u8 *cp = bounce; int sr; + u8 instr; *cp = AT25_WREN; status = spi_write(at25->spi, cp, 1); @@ -184,6 +191,12 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off, break; } + instr = AT25_WRITE; + if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR) + if (offset >= (1U << (at25->addrlen * 8))) + instr |= AT25_INSTR_BIT3; + *cp++ = instr; + /* 8/16/24-bit address is written MSB first */ switch (at25->addrlen) { default: /* case 3 */ diff --git a/drivers/misc/max8997-muic.c b/drivers/misc/max8997-muic.c deleted file mode 100644 index 19591eaa492a..000000000000 --- a/drivers/misc/max8997-muic.c +++ /dev/null @@ -1,495 +0,0 @@ -/* - * max8997-muic.c - MAX8997 muic driver for the Maxim 8997 - * - * Copyright (C) 2011 Samsung Electrnoics - * Donggeun Kim <dg77.kim@samsung.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/i2c.h> -#include <linux/slab.h> -#include <linux/interrupt.h> -#include <linux/err.h> -#include <linux/platform_device.h> -#include <linux/kobject.h> -#include <linux/mfd/max8997.h> -#include <linux/mfd/max8997-private.h> - -/* MAX8997-MUIC STATUS1 register */ -#define STATUS1_ADC_SHIFT 0 -#define STATUS1_ADCLOW_SHIFT 5 -#define STATUS1_ADCERR_SHIFT 6 -#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) - -/* MAX8997-MUIC STATUS2 register */ -#define STATUS2_CHGTYP_SHIFT 0 -#define STATUS2_CHGDETRUN_SHIFT 3 -#define STATUS2_DCDTMR_SHIFT 4 -#define STATUS2_DBCHG_SHIFT 5 -#define STATUS2_VBVOLT_SHIFT 6 -#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) -#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) - -/* MAX8997-MUIC STATUS3 register */ -#define STATUS3_OVP_SHIFT 2 -#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) - -/* MAX8997-MUIC CONTROL1 register */ -#define COMN1SW_SHIFT 0 -#define COMP2SW_SHIFT 3 -#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) -#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define SW_MASK (COMP2SW_MASK | COMN1SW_MASK) - -#define MAX8997_SW_USB ((1 << COMP2SW_SHIFT) | (1 << COMN1SW_SHIFT)) -#define MAX8997_SW_AUDIO ((2 << COMP2SW_SHIFT) | (2 << COMN1SW_SHIFT)) -#define MAX8997_SW_UART ((3 << COMP2SW_SHIFT) | (3 << COMN1SW_SHIFT)) -#define MAX8997_SW_OPEN ((0 << COMP2SW_SHIFT) | (0 << COMN1SW_SHIFT)) - -#define MAX8997_ADC_GROUND 0x00 -#define MAX8997_ADC_MHL 0x01 -#define MAX8997_ADC_JIG_USB_1 0x18 -#define MAX8997_ADC_JIG_USB_2 0x19 -#define MAX8997_ADC_DESKDOCK 0x1a -#define MAX8997_ADC_JIG_UART 0x1c -#define MAX8997_ADC_CARDOCK 0x1d -#define MAX8997_ADC_OPEN 0x1f - -struct max8997_muic_irq { - unsigned int irq; - const char *name; -}; - -static struct max8997_muic_irq muic_irqs[] = { - { MAX8997_MUICIRQ_ADCError, "muic-ADC_error" }, - { MAX8997_MUICIRQ_ADCLow, "muic-ADC_low" }, - { MAX8997_MUICIRQ_ADC, "muic-ADC" }, - { MAX8997_MUICIRQ_VBVolt, "muic-VB_voltage" }, - { MAX8997_MUICIRQ_DBChg, "muic-DB_charger" }, - { MAX8997_MUICIRQ_DCDTmr, "muic-DCD_timer" }, - { MAX8997_MUICIRQ_ChgDetRun, "muic-CDR_status" }, - { MAX8997_MUICIRQ_ChgTyp, "muic-charger_type" }, - { MAX8997_MUICIRQ_OVP, "muic-over_voltage" }, -}; - -struct max8997_muic_info { - struct device *dev; - struct max8997_dev *iodev; - struct i2c_client *muic; - struct max8997_muic_platform_data *muic_pdata; - - int irq; - struct work_struct irq_work; - - enum max8997_muic_charger_type pre_charger_type; - int pre_adc; - - struct mutex mutex; -}; - -static int max8997_muic_handle_usb(struct max8997_muic_info *info, - enum max8997_muic_usb_type usb_type, bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int ret = 0; - - if (usb_type == MAX8997_USB_HOST) { - /* switch to USB */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_USB : MAX8997_SW_OPEN, - SW_MASK); - if (ret) { - dev_err(info->dev, "failed to update muic register\n"); - goto out; - } - } - - if (mdata->usb_callback) - mdata->usb_callback(usb_type, attached); -out: - return ret; -} - -static void max8997_muic_handle_mhl(struct max8997_muic_info *info, - bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - - if (mdata->mhl_callback) - mdata->mhl_callback(attached); -} - -static int max8997_muic_handle_dock(struct max8997_muic_info *info, - int adc, bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int ret = 0; - - /* switch to AUDIO */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_AUDIO : MAX8997_SW_OPEN, - SW_MASK); - if (ret) { - dev_err(info->dev, "failed to update muic register\n"); - goto out; - } - - switch (adc) { - case MAX8997_ADC_DESKDOCK: - if (mdata->deskdock_callback) - mdata->deskdock_callback(attached); - break; - case MAX8997_ADC_CARDOCK: - if (mdata->cardock_callback) - mdata->cardock_callback(attached); - break; - default: - break; - } -out: - return ret; -} - -static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, - bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int ret = 0; - - /* switch to UART */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_UART : MAX8997_SW_OPEN, - SW_MASK); - if (ret) { - dev_err(info->dev, "failed to update muic register\n"); - goto out; - } - - if (mdata->uart_callback) - mdata->uart_callback(attached); -out: - return ret; -} - -static int max8997_muic_handle_adc_detach(struct max8997_muic_info *info) -{ - int ret = 0; - - switch (info->pre_adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, false); - break; - case MAX8997_ADC_MHL: - max8997_muic_handle_mhl(info, false); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, false); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, info->pre_adc, false); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, false); - break; - default: - break; - } - - return ret; -} - -static int max8997_muic_handle_adc(struct max8997_muic_info *info, int adc) -{ - int ret = 0; - - switch (adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, true); - break; - case MAX8997_ADC_MHL: - max8997_muic_handle_mhl(info, true); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, true); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, adc, true); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, true); - break; - case MAX8997_ADC_OPEN: - ret = max8997_muic_handle_adc_detach(info); - break; - default: - break; - } - - info->pre_adc = adc; - - return ret; -} - -static int max8997_muic_handle_charger_type(struct max8997_muic_info *info, - enum max8997_muic_charger_type charger_type) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - u8 adc; - int ret; - - ret = max8997_read_reg(info->muic, MAX8997_MUIC_REG_STATUS1, &adc); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - goto out; - } - - switch (charger_type) { - case MAX8997_CHARGER_TYPE_NONE: - if (mdata->charger_callback) - mdata->charger_callback(false, charger_type); - if (info->pre_charger_type == MAX8997_CHARGER_TYPE_USB) { - max8997_muic_handle_usb(info, - MAX8997_USB_DEVICE, false); - } - break; - case MAX8997_CHARGER_TYPE_USB: - if ((adc & STATUS1_ADC_MASK) == MAX8997_ADC_OPEN) { - max8997_muic_handle_usb(info, - MAX8997_USB_DEVICE, true); - } - if (mdata->charger_callback) - mdata->charger_callback(true, charger_type); - break; - case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: - case MAX8997_CHARGER_TYPE_DEDICATED_CHG: - case MAX8997_CHARGER_TYPE_500MA: - case MAX8997_CHARGER_TYPE_1A: - if (mdata->charger_callback) - mdata->charger_callback(true, charger_type); - break; - default: - break; - } - - info->pre_charger_type = charger_type; -out: - return ret; -} - -static void max8997_muic_irq_work(struct work_struct *work) -{ - struct max8997_muic_info *info = container_of(work, - struct max8997_muic_info, irq_work); - struct max8997_platform_data *pdata = - dev_get_platdata(info->iodev->dev); - u8 status[3]; - u8 adc, chg_type; - - int irq_type = info->irq - pdata->irq_base; - int ret; - - mutex_lock(&info->mutex); - - ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 3, status); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - mutex_unlock(&info->mutex); - return; - } - - dev_dbg(info->dev, "%s: STATUS1:0x%x, 2:0x%x\n", __func__, - status[0], status[1]); - - switch (irq_type) { - case MAX8997_MUICIRQ_ADC: - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - max8997_muic_handle_adc(info, adc); - break; - case MAX8997_MUICIRQ_ChgTyp: - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - max8997_muic_handle_charger_type(info, chg_type); - break; - default: - dev_info(info->dev, "misc interrupt: %s occurred\n", - muic_irqs[irq_type].name); - break; - } - - mutex_unlock(&info->mutex); - - return; -} - -static irqreturn_t max8997_muic_irq_handler(int irq, void *data) -{ - struct max8997_muic_info *info = data; - - dev_dbg(info->dev, "irq:%d\n", irq); - info->irq = irq; - - schedule_work(&info->irq_work); - - return IRQ_HANDLED; -} - -static void max8997_muic_detect_dev(struct max8997_muic_info *info) -{ - int ret; - u8 status[2], adc, chg_type; - - ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 2, status); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - return; - } - - dev_info(info->dev, "STATUS1:0x%x, STATUS2:0x%x\n", - status[0], status[1]); - - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - max8997_muic_handle_adc(info, adc); - max8997_muic_handle_charger_type(info, chg_type); -} - -static void max8997_initialize_device(struct max8997_muic_info *info) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int i; - - for (i = 0; i < mdata->num_init_data; i++) { - max8997_write_reg(info->muic, mdata->init_data[i].addr, - mdata->init_data[i].data); - } -} - -static int __devinit max8997_muic_probe(struct platform_device *pdev) -{ - struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); - struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); - struct max8997_muic_info *info; - int ret, i; - - info = kzalloc(sizeof(struct max8997_muic_info), GFP_KERNEL); - if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - ret = -ENOMEM; - goto err_kfree; - } - - if (!pdata->muic_pdata) { - dev_err(&pdev->dev, "failed to get platform_data\n"); - ret = -EINVAL; - goto err_pdata; - } - info->muic_pdata = pdata->muic_pdata; - - info->dev = &pdev->dev; - info->iodev = iodev; - info->muic = iodev->muic; - - platform_set_drvdata(pdev, info); - mutex_init(&info->mutex); - - INIT_WORK(&info->irq_work, max8997_muic_irq_work); - - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { - struct max8997_muic_irq *muic_irq = &muic_irqs[i]; - - ret = request_threaded_irq(pdata->irq_base + muic_irq->irq, - NULL, max8997_muic_irq_handler, - 0, muic_irq->name, - info); - if (ret) { - dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", - muic_irq->irq, ret); - - for (i = i - 1; i >= 0; i--) - free_irq(muic_irq->irq, info); - - goto err_irq; - } - } - - /* Initialize registers according to platform data */ - max8997_initialize_device(info); - - /* Initial device detection */ - max8997_muic_detect_dev(info); - - return ret; - -err_irq: -err_pdata: - kfree(info); -err_kfree: - return ret; -} - -static int __devexit max8997_muic_remove(struct platform_device *pdev) -{ - struct max8997_muic_info *info = platform_get_drvdata(pdev); - struct max8997_platform_data *pdata = - dev_get_platdata(info->iodev->dev); - int i; - - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) - free_irq(pdata->irq_base + muic_irqs[i].irq, info); - cancel_work_sync(&info->irq_work); - - kfree(info); - - return 0; -} - -static struct platform_driver max8997_muic_driver = { - .driver = { - .name = "max8997-muic", - .owner = THIS_MODULE, - }, - .probe = max8997_muic_probe, - .remove = __devexit_p(max8997_muic_remove), -}; - -module_platform_driver(max8997_muic_driver); - -MODULE_DESCRIPTION("Maxim MAX8997 MUIC driver"); -MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); -MODULE_LICENSE("GPL"); diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig new file mode 100644 index 000000000000..47d78a72db2e --- /dev/null +++ b/drivers/misc/mei/Kconfig @@ -0,0 +1,28 @@ +config INTEL_MEI + tristate "Intel Management Engine Interface (Intel MEI)" + depends on X86 && PCI && EXPERIMENTAL && WATCHDOG_CORE + help + The Intel Management Engine (Intel ME) provides Manageability, + Security and Media services for system containing Intel chipsets. + if selected /dev/mei misc device will be created. + + Supported Chipsets are: + 7 Series Chipset Family + 6 Series Chipset Family + 5 Series Chipset Family + 4 Series Chipset Family + Mobile 4 Series Chipset Family + ICH9 + 82946GZ/GL + 82G35 Express + 82Q963/Q965 + 82P965/G965 + Mobile PM965/GM965 + Mobile GME965/GLE960 + 82Q35 Express + 82G33/G31/P35/P31 Express + 82Q33 Express + 82X38/X48 Express + + For more information see + <http://software.intel.com/en-us/manageability/> diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile new file mode 100644 index 000000000000..57168db6c7e5 --- /dev/null +++ b/drivers/misc/mei/Makefile @@ -0,0 +1,11 @@ +# +# Makefile - Intel Management Engine Interface (Intel MEI) Linux driver +# Copyright (c) 2010-2011, Intel Corporation. +# +obj-$(CONFIG_INTEL_MEI) += mei.o +mei-objs := init.o +mei-objs += interrupt.o +mei-objs += interface.o +mei-objs += iorw.o +mei-objs += main.o +mei-objs += wd.o diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h new file mode 100644 index 000000000000..24c4c962819e --- /dev/null +++ b/drivers/misc/mei/hw.h @@ -0,0 +1,332 @@ +/* + * + * 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. + * + */ + +#ifndef _MEI_HW_TYPES_H_ +#define _MEI_HW_TYPES_H_ + +#include <linux/uuid.h> + +/* + * Timeouts + */ +#define MEI_INTEROP_TIMEOUT (HZ * 7) +#define MEI_CONNECT_TIMEOUT 3 /* at least 2 seconds */ + +#define CONNECT_TIMEOUT 15 /* HPS definition */ +#define INIT_CLIENTS_TIMEOUT 15 /* HPS definition */ + +#define IAMTHIF_STALL_TIMER 12 /* seconds */ +#define IAMTHIF_READ_TIMER 10000 /* ms */ + +/* + * Internal Clients Number + */ +#define MEI_WD_HOST_CLIENT_ID 1 +#define MEI_IAMTHIF_HOST_CLIENT_ID 2 + +/* + * MEI device IDs + */ +#define MEI_DEV_ID_82946GZ 0x2974 /* 82946GZ/GL */ +#define MEI_DEV_ID_82G35 0x2984 /* 82G35 Express */ +#define MEI_DEV_ID_82Q965 0x2994 /* 82Q963/Q965 */ +#define MEI_DEV_ID_82G965 0x29A4 /* 82P965/G965 */ + +#define MEI_DEV_ID_82GM965 0x2A04 /* Mobile PM965/GM965 */ +#define MEI_DEV_ID_82GME965 0x2A14 /* Mobile GME965/GLE960 */ + +#define MEI_DEV_ID_ICH9_82Q35 0x29B4 /* 82Q35 Express */ +#define MEI_DEV_ID_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */ +#define MEI_DEV_ID_ICH9_82Q33 0x29D4 /* 82Q33 Express */ +#define MEI_DEV_ID_ICH9_82X38 0x29E4 /* 82X38/X48 Express */ +#define MEI_DEV_ID_ICH9_3200 0x29F4 /* 3200/3210 Server */ + +#define MEI_DEV_ID_ICH9_6 0x28B4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_7 0x28C4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_8 0x28D4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_9 0x28E4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_10 0x28F4 /* Bearlake */ + +#define MEI_DEV_ID_ICH9M_1 0x2A44 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_2 0x2A54 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_3 0x2A64 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_4 0x2A74 /* Cantiga */ + +#define MEI_DEV_ID_ICH10_1 0x2E04 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_2 0x2E14 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_3 0x2E24 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_4 0x2E34 /* Eaglelake */ + +#define MEI_DEV_ID_IBXPK_1 0x3B64 /* Calpella */ +#define MEI_DEV_ID_IBXPK_2 0x3B65 /* Calpella */ + +#define MEI_DEV_ID_CPT_1 0x1C3A /* Cougerpoint */ +#define MEI_DEV_ID_PBG_1 0x1D3A /* PBG */ + +#define MEI_DEV_ID_PPT_1 0x1E3A /* Pantherpoint PPT */ +#define MEI_DEV_ID_PPT_2 0x1CBA /* Pantherpoint PPT */ +#define MEI_DEV_ID_PPT_3 0x1DBA /* Pantherpoint PPT */ + + +/* + * MEI HW Section + */ + +/* MEI registers */ +/* H_CB_WW - Host Circular Buffer (CB) Write Window register */ +#define H_CB_WW 0 +/* H_CSR - Host Control Status register */ +#define H_CSR 4 +/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */ +#define ME_CB_RW 8 +/* ME_CSR_HA - ME Control Status Host Access register (read only) */ +#define ME_CSR_HA 0xC + + +/* register bits of H_CSR (Host Control Status register) */ +/* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ +#define H_CBD 0xFF000000 +/* Host Circular Buffer Write Pointer */ +#define H_CBWP 0x00FF0000 +/* Host Circular Buffer Read Pointer */ +#define H_CBRP 0x0000FF00 +/* Host Reset */ +#define H_RST 0x00000010 +/* Host Ready */ +#define H_RDY 0x00000008 +/* Host Interrupt Generate */ +#define H_IG 0x00000004 +/* Host Interrupt Status */ +#define H_IS 0x00000002 +/* Host Interrupt Enable */ +#define H_IE 0x00000001 + + +/* register bits of ME_CSR_HA (ME Control Status Host Access register) */ +/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only +access to ME_CBD */ +#define ME_CBD_HRA 0xFF000000 +/* ME CB Write Pointer HRA - host read only access to ME_CBWP */ +#define ME_CBWP_HRA 0x00FF0000 +/* ME CB Read Pointer HRA - host read only access to ME_CBRP */ +#define ME_CBRP_HRA 0x0000FF00 +/* ME Reset HRA - host read only access to ME_RST */ +#define ME_RST_HRA 0x00000010 +/* ME Ready HRA - host read only access to ME_RDY */ +#define ME_RDY_HRA 0x00000008 +/* ME Interrupt Generate HRA - host read only access to ME_IG */ +#define ME_IG_HRA 0x00000004 +/* ME Interrupt Status HRA - host read only access to ME_IS */ +#define ME_IS_HRA 0x00000002 +/* ME Interrupt Enable HRA - host read only access to ME_IE */ +#define ME_IE_HRA 0x00000001 + +/* + * MEI Version + */ +#define HBM_MINOR_VERSION 0 +#define HBM_MAJOR_VERSION 1 +#define HBM_TIMEOUT 1 /* 1 second */ + +/* Host bus message command opcode */ +#define MEI_HBM_CMD_OP_MSK 0x7f +/* Host bus message command RESPONSE */ +#define MEI_HBM_CMD_RES_MSK 0x80 + +/* + * MEI Bus Message Command IDs + */ +#define HOST_START_REQ_CMD 0x01 +#define HOST_START_RES_CMD 0x81 + +#define HOST_STOP_REQ_CMD 0x02 +#define HOST_STOP_RES_CMD 0x82 + +#define ME_STOP_REQ_CMD 0x03 + +#define HOST_ENUM_REQ_CMD 0x04 +#define HOST_ENUM_RES_CMD 0x84 + +#define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05 +#define HOST_CLIENT_PROPERTIES_RES_CMD 0x85 + +#define CLIENT_CONNECT_REQ_CMD 0x06 +#define CLIENT_CONNECT_RES_CMD 0x86 + +#define CLIENT_DISCONNECT_REQ_CMD 0x07 +#define CLIENT_DISCONNECT_RES_CMD 0x87 + +#define MEI_FLOW_CONTROL_CMD 0x08 + +/* + * MEI Stop Reason + * used by hbm_host_stop_request.reason + */ +enum mei_stop_reason_types { + DRIVER_STOP_REQUEST = 0x00, + DEVICE_D1_ENTRY = 0x01, + DEVICE_D2_ENTRY = 0x02, + DEVICE_D3_ENTRY = 0x03, + SYSTEM_S1_ENTRY = 0x04, + SYSTEM_S2_ENTRY = 0x05, + SYSTEM_S3_ENTRY = 0x06, + SYSTEM_S4_ENTRY = 0x07, + SYSTEM_S5_ENTRY = 0x08 +}; + +/* + * Client Connect Status + * used by hbm_client_connect_response.status + */ +enum client_connect_status_types { + CCS_SUCCESS = 0x00, + CCS_NOT_FOUND = 0x01, + CCS_ALREADY_STARTED = 0x02, + CCS_OUT_OF_RESOURCES = 0x03, + CCS_MESSAGE_SMALL = 0x04 +}; + +/* + * Client Disconnect Status + */ +enum client_disconnect_status_types { + CDS_SUCCESS = 0x00 +}; + +/* + * MEI BUS Interface Section + */ +struct mei_msg_hdr { + u32 me_addr:8; + u32 host_addr:8; + u32 length:9; + u32 reserved:6; + u32 msg_complete:1; +} __packed; + + +struct mei_bus_message { + u8 hbm_cmd; + u8 data[0]; +} __packed; + +struct hbm_version { + u8 minor_version; + u8 major_version; +} __packed; + +struct hbm_host_version_request { + u8 hbm_cmd; + u8 reserved; + struct hbm_version host_version; +} __packed; + +struct hbm_host_version_response { + u8 hbm_cmd; + u8 host_version_supported; + struct hbm_version me_max_version; +} __packed; + +struct hbm_host_stop_request { + u8 hbm_cmd; + u8 reason; + u8 reserved[2]; +} __packed; + +struct hbm_host_stop_response { + u8 hbm_cmd; + u8 reserved[3]; +} __packed; + +struct hbm_me_stop_request { + u8 hbm_cmd; + u8 reason; + u8 reserved[2]; +} __packed; + +struct hbm_host_enum_request { + u8 hbm_cmd; + u8 reserved[3]; +} __packed; + +struct hbm_host_enum_response { + u8 hbm_cmd; + u8 reserved[3]; + u8 valid_addresses[32]; +} __packed; + +struct mei_client_properties { + uuid_le protocol_name; + u8 protocol_version; + u8 max_number_of_connections; + u8 fixed_address; + u8 single_recv_buf; + u32 max_msg_length; +} __packed; + +struct hbm_props_request { + u8 hbm_cmd; + u8 address; + u8 reserved[2]; +} __packed; + + +struct hbm_props_response { + u8 hbm_cmd; + u8 address; + u8 status; + u8 reserved[1]; + struct mei_client_properties client_properties; +} __packed; + +struct hbm_client_connect_request { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 reserved; +} __packed; + +struct hbm_client_connect_response { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 status; +} __packed; + +struct hbm_client_disconnect_request { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 reserved[1]; +} __packed; + +#define MEI_FC_MESSAGE_RESERVED_LENGTH 5 + +struct hbm_flow_control { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 reserved[MEI_FC_MESSAGE_RESERVED_LENGTH]; +} __packed; + +struct mei_me_client { + struct mei_client_properties props; + u8 client_id; + u8 mei_flow_ctrl_creds; +} __packed; + + +#endif diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c new file mode 100644 index 000000000000..a7d0bb0880ec --- /dev/null +++ b/drivers/misc/mei/init.c @@ -0,0 +1,735 @@ +/* + * + * 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/pci.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/delay.h> + +#include "mei_dev.h" +#include "hw.h" +#include "interface.h" +#include <linux/mei.h> + +const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, + 0xa8, 0x46, 0xe0, 0xff, 0x65, + 0x81, 0x4c); + +/** + * mei_io_list_init - Sets up a queue list. + * + * @list: An instance io list structure + * @dev: the device structure + */ +void mei_io_list_init(struct mei_io_list *list) +{ + /* initialize our queue list */ + INIT_LIST_HEAD(&list->mei_cb.cb_list); +} + +/** + * mei_io_list_flush - removes list entry belonging to cl. + * + * @list: An instance of our list structure + * @cl: private data of the file object + */ +void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl) +{ + struct mei_cl_cb *pos; + struct mei_cl_cb *next; + + list_for_each_entry_safe(pos, next, &list->mei_cb.cb_list, cb_list) { + if (pos->file_private) { + struct mei_cl *cl_tmp; + cl_tmp = (struct mei_cl *)pos->file_private; + if (mei_cl_cmp_id(cl, cl_tmp)) + list_del(&pos->cb_list); + } + } +} +/** + * mei_cl_flush_queues - flushes queue lists belonging to cl. + * + * @dev: the device structure + * @cl: private data of the file object + */ +int mei_cl_flush_queues(struct mei_cl *cl) +{ + if (!cl || !cl->dev) + return -EINVAL; + + dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); + mei_io_list_flush(&cl->dev->read_list, cl); + mei_io_list_flush(&cl->dev->write_list, cl); + mei_io_list_flush(&cl->dev->write_waiting_list, cl); + 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->amthi_cmd_list, cl); + mei_io_list_flush(&cl->dev->amthi_read_complete_list, cl); + return 0; +} + + + +/** + * mei_reset_iamthif_params - initializes mei device iamthif + * + * @dev: the device structure + */ +static void mei_reset_iamthif_params(struct mei_device *dev) +{ + /* reset iamthif parameters. */ + dev->iamthif_current_cb = NULL; + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = false; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; +} + +/** + * init_mei_device - allocates and initializes the mei device structure + * + * @pdev: The pci device structure + * + * returns The mei_device_device pointer on success, NULL on failure. + */ +struct mei_device *mei_device_init(struct pci_dev *pdev) +{ + struct mei_device *dev; + + dev = kzalloc(sizeof(struct mei_device), GFP_KERNEL); + if (!dev) + return NULL; + + /* setup our list array */ + INIT_LIST_HEAD(&dev->file_list); + INIT_LIST_HEAD(&dev->wd_cl.link); + INIT_LIST_HEAD(&dev->iamthif_cl.link); + mutex_init(&dev->device_lock); + init_waitqueue_head(&dev->wait_recvd_msg); + init_waitqueue_head(&dev->wait_stop_wd); + dev->mei_state = MEI_INITIALIZING; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->wd_interface_reg = false; + + + mei_io_list_init(&dev->read_list); + mei_io_list_init(&dev->write_list); + mei_io_list_init(&dev->write_waiting_list); + mei_io_list_init(&dev->ctrl_wr_list); + mei_io_list_init(&dev->ctrl_rd_list); + mei_io_list_init(&dev->amthi_cmd_list); + mei_io_list_init(&dev->amthi_read_complete_list); + dev->pdev = pdev; + return dev; +} + +/** + * mei_hw_init - initializes host and fw to start work. + * + * @dev: the device structure + * + * returns 0 on success, <0 on failure. + */ +int mei_hw_init(struct mei_device *dev) +{ + int err = 0; + int ret; + + mutex_lock(&dev->device_lock); + + dev->host_hw_state = mei_hcsr_read(dev); + dev->me_hw_state = mei_mecsr_read(dev); + dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, mestate = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + /* acknowledge interrupt and stop interupts */ + if ((dev->host_hw_state & H_IS) == H_IS) + mei_reg_write(dev, H_CSR, dev->host_hw_state); + + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); + + mei_reset(dev, 1); + + dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + /* wait for ME to turn on ME_RDY */ + if (!dev->recvd_msg) { + mutex_unlock(&dev->device_lock); + err = wait_event_interruptible_timeout(dev->wait_recvd_msg, + dev->recvd_msg, MEI_INTEROP_TIMEOUT); + mutex_lock(&dev->device_lock); + } + + if (err <= 0 && !dev->recvd_msg) { + dev->mei_state = MEI_DISABLED; + dev_dbg(&dev->pdev->dev, + "wait_event_interruptible_timeout failed" + "on wait for ME to turn on ME_RDY.\n"); + ret = -ENODEV; + goto out; + } + + if (!(((dev->host_hw_state & H_RDY) == H_RDY) && + ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA))) { + dev->mei_state = MEI_DISABLED; + dev_dbg(&dev->pdev->dev, + "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + if (!(dev->host_hw_state & H_RDY)) + dev_dbg(&dev->pdev->dev, "host turn off H_RDY.\n"); + + if (!(dev->me_hw_state & ME_RDY_HRA)) + dev_dbg(&dev->pdev->dev, "ME turn off ME_RDY.\n"); + + dev_err(&dev->pdev->dev, "link layer initialization failed.\n"); + ret = -ENODEV; + goto out; + } + + if (dev->version.major_version != HBM_MAJOR_VERSION || + dev->version.minor_version != HBM_MINOR_VERSION) { + dev_dbg(&dev->pdev->dev, "MEI start failed.\n"); + ret = -ENODEV; + goto out; + } + + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + dev_dbg(&dev->pdev->dev, "ME turn on ME_RDY and host turn on H_RDY.\n"); + dev_dbg(&dev->pdev->dev, "link layer has been established.\n"); + dev_dbg(&dev->pdev->dev, "MEI start success.\n"); + ret = 0; + +out: + mutex_unlock(&dev->device_lock); + return ret; +} + +/** + * mei_hw_reset - resets fw via mei csr register. + * + * @dev: the device structure + * @interrupts_enabled: if interrupt should be enabled after reset. + */ +static void mei_hw_reset(struct mei_device *dev, int interrupts_enabled) +{ + dev->host_hw_state |= (H_RST | H_IG); + + if (interrupts_enabled) + mei_enable_interrupts(dev); + else + mei_disable_interrupts(dev); +} + +/** + * mei_reset - resets host and fw. + * + * @dev: the device structure + * @interrupts_enabled: if interrupt should be enabled after reset. + */ +void mei_reset(struct mei_device *dev, int interrupts_enabled) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb_next = NULL; + bool unexpected; + + if (dev->mei_state == MEI_RECOVERING_FROM_RESET) { + dev->need_reset = true; + return; + } + + unexpected = (dev->mei_state != MEI_INITIALIZING && + dev->mei_state != MEI_DISABLED && + dev->mei_state != MEI_POWER_DOWN && + dev->mei_state != MEI_POWER_UP); + + dev->host_hw_state = mei_hcsr_read(dev); + + dev_dbg(&dev->pdev->dev, "before reset host_hw_state = 0x%08x.\n", + dev->host_hw_state); + + mei_hw_reset(dev, interrupts_enabled); + + dev->host_hw_state &= ~H_RST; + dev->host_hw_state |= H_IG; + + mei_hcsr_set(dev); + + dev_dbg(&dev->pdev->dev, "currently saved host_hw_state = 0x%08x.\n", + dev->host_hw_state); + + dev->need_reset = false; + + if (dev->mei_state != MEI_INITIALIZING) { + if (dev->mei_state != MEI_DISABLED && + dev->mei_state != MEI_POWER_DOWN) + dev->mei_state = MEI_RESETING; + + list_for_each_entry_safe(cl_pos, + cl_next, &dev->file_list, link) { + cl_pos->state = MEI_FILE_DISCONNECTED; + cl_pos->mei_flow_ctrl_creds = 0; + cl_pos->read_cb = NULL; + cl_pos->timer_count = 0; + } + /* remove entry if already in list */ + dev_dbg(&dev->pdev->dev, "list del iamthif and wd file list.\n"); + mei_remove_client_from_file_list(dev, + dev->wd_cl.host_client_id); + + mei_remove_client_from_file_list(dev, + dev->iamthif_cl.host_client_id); + + mei_reset_iamthif_params(dev); + dev->wd_due_counter = 0; + dev->extra_write_index = 0; + } + + dev->me_clients_num = 0; + dev->rd_msg_hdr = 0; + dev->stop = false; + dev->wd_pending = false; + + /* update the state of the registers after reset */ + dev->host_hw_state = mei_hcsr_read(dev); + dev->me_hw_state = mei_mecsr_read(dev); + + dev_dbg(&dev->pdev->dev, "after reset host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + if (unexpected) + dev_warn(&dev->pdev->dev, "unexpected reset.\n"); + + /* Wake up all readings so they can be interrupted */ + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (waitqueue_active(&cl_pos->rx_wait)) { + dev_dbg(&dev->pdev->dev, "Waking up client!\n"); + wake_up_interruptible(&cl_pos->rx_wait); + } + } + /* remove all waiting requests */ + list_for_each_entry_safe(cb_pos, cb_next, + &dev->write_list.mei_cb.cb_list, cb_list) { + list_del(&cb_pos->cb_list); + mei_free_cb_private(cb_pos); + } +} + + + +/** + * host_start_message - mei host sends start message. + * + * @dev: the device structure + * + * returns none. + */ +void mei_host_start_message(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_host_version_request *host_start_req; + + /* host start message */ + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_version_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + host_start_req = + (struct hbm_host_version_request *) &dev->wr_msg_buf[1]; + memset(host_start_req, 0, sizeof(struct hbm_host_version_request)); + host_start_req->hbm_cmd = HOST_START_REQ_CMD; + host_start_req->host_version.major_version = HBM_MAJOR_VERSION; + host_start_req->host_version.minor_version = HBM_MINOR_VERSION; + dev->recvd_msg = false; + if (mei_write_message(dev, mei_hdr, (unsigned char *)host_start_req, + mei_hdr->length)) { + dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); + dev->mei_state = MEI_RESETING; + mei_reset(dev, 1); + } + dev->init_clients_state = MEI_START_MESSAGE; + dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + return ; +} + +/** + * host_enum_clients_message - host sends enumeration client request message. + * + * @dev: the device structure + * + * returns none. + */ +void mei_host_enum_clients_message(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_host_enum_request *host_enum_req; + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + /* enumerate clients */ + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_enum_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + host_enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1]; + memset(host_enum_req, 0, sizeof(struct hbm_host_enum_request)); + host_enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; + if (mei_write_message(dev, mei_hdr, (unsigned char *)host_enum_req, + mei_hdr->length)) { + dev->mei_state = MEI_RESETING; + dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); + mei_reset(dev, 1); + } + dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE; + dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + return; +} + + +/** + * allocate_me_clients_storage - allocates storage for me clients + * + * @dev: the device structure + * + * returns none. + */ +void mei_allocate_me_clients_storage(struct mei_device *dev) +{ + struct mei_me_client *clients; + int b; + + /* count how many ME clients we have */ + for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) + dev->me_clients_num++; + + if (dev->me_clients_num <= 0) + return ; + + + if (dev->me_clients != NULL) { + kfree(dev->me_clients); + dev->me_clients = NULL; + } + dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\n", + dev->me_clients_num * sizeof(struct mei_me_client)); + /* allocate storage for ME clients representation */ + clients = kcalloc(dev->me_clients_num, + sizeof(struct mei_me_client), GFP_KERNEL); + if (!clients) { + dev_dbg(&dev->pdev->dev, "memory allocation for ME clients failed.\n"); + dev->mei_state = MEI_RESETING; + mei_reset(dev, 1); + return ; + } + dev->me_clients = clients; + return ; +} +/** + * host_client_properties - reads properties for client + * + * @dev: the device structure + * + * returns: + * < 0 - Error. + * = 0 - no more clients. + * = 1 - still have clients to send properties request. + */ +int mei_host_client_properties(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_header; + struct hbm_props_request *host_cli_req; + int b; + u8 client_num = dev->me_client_presentation_num; + + b = dev->me_client_index; + b = find_next_bit(dev->me_clients_map, MEI_CLIENTS_MAX, b); + if (b < MEI_CLIENTS_MAX) { + dev->me_clients[client_num].client_id = b; + dev->me_clients[client_num].mei_flow_ctrl_creds = 0; + mei_header = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; + mei_header->host_addr = 0; + mei_header->me_addr = 0; + mei_header->length = sizeof(struct hbm_props_request); + mei_header->msg_complete = 1; + mei_header->reserved = 0; + + host_cli_req = (struct hbm_props_request *)&dev->wr_msg_buf[1]; + + memset(host_cli_req, 0, sizeof(struct hbm_props_request)); + + host_cli_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; + host_cli_req->address = b; + + if (mei_write_message(dev, mei_header, + (unsigned char *)host_cli_req, + mei_header->length)) { + dev->mei_state = MEI_RESETING; + dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); + mei_reset(dev, 1); + return -EIO; + } + + dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + dev->me_client_index = b; + return 1; + } + + return 0; +} + +/** + * mei_init_file_private - initializes private file structure. + * + * @priv: private file structure to be initialized + * @file: the file structure + */ +void mei_cl_init(struct mei_cl *priv, struct mei_device *dev) +{ + memset(priv, 0, sizeof(struct mei_cl)); + init_waitqueue_head(&priv->wait); + init_waitqueue_head(&priv->rx_wait); + init_waitqueue_head(&priv->tx_wait); + INIT_LIST_HEAD(&priv->link); + priv->reading_state = MEI_IDLE; + priv->writing_state = MEI_IDLE; + priv->dev = dev; +} + +int mei_find_me_client_index(const struct mei_device *dev, uuid_le cuuid) +{ + int i, res = -1; + + for (i = 0; i < dev->me_clients_num; ++i) + if (uuid_le_cmp(cuuid, + dev->me_clients[i].props.protocol_name) == 0) { + res = i; + break; + } + + return res; +} + + +/** + * mei_find_me_client_update_filext - searches for ME client guid + * sets client_id in mei_file_private if found + * @dev: the device structure + * @priv: private file structure to set client_id in + * @cguid: searched guid of ME client + * @client_id: id of host client to be set in file private structure + * + * returns ME client index + */ +u8 mei_find_me_client_update_filext(struct mei_device *dev, struct mei_cl *priv, + const uuid_le *cguid, u8 client_id) +{ + int i; + + if (!dev || !priv || !cguid) + return 0; + + /* check for valid client id */ + i = mei_find_me_client_index(dev, *cguid); + if (i >= 0) { + priv->me_client_id = dev->me_clients[i].client_id; + priv->state = MEI_FILE_CONNECTING; + priv->host_client_id = client_id; + + list_add_tail(&priv->link, &dev->file_list); + return (u8)i; + } + + return 0; +} + +/** + * host_init_iamthif - mei initialization iamthif client. + * + * @dev: the device structure + * + */ +void mei_host_init_iamthif(struct mei_device *dev) +{ + u8 i; + unsigned char *msg_buf; + + mei_cl_init(&dev->iamthif_cl, dev); + dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; + + /* find ME amthi client */ + i = mei_find_me_client_update_filext(dev, &dev->iamthif_cl, + &mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID); + if (dev->iamthif_cl.state != MEI_FILE_CONNECTING) { + dev_dbg(&dev->pdev->dev, "failed to find iamthif client.\n"); + return; + } + + /* Assign iamthif_mtu to the value received from ME */ + + dev->iamthif_mtu = dev->me_clients[i].props.max_msg_length; + dev_dbg(&dev->pdev->dev, "IAMTHIF_MTU = %d\n", + dev->me_clients[i].props.max_msg_length); + + kfree(dev->iamthif_msg_buf); + dev->iamthif_msg_buf = NULL; + + /* allocate storage for ME message buffer */ + msg_buf = kcalloc(dev->iamthif_mtu, + sizeof(unsigned char), GFP_KERNEL); + if (!msg_buf) { + dev_dbg(&dev->pdev->dev, "memory allocation for ME message buffer failed.\n"); + return; + } + + dev->iamthif_msg_buf = msg_buf; + + if (mei_connect(dev, &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n"); + dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; + dev->iamthif_cl.host_client_id = 0; + } else { + dev->iamthif_cl.timer_count = CONNECT_TIMEOUT; + } +} + +/** + * mei_alloc_file_private - allocates a private file structure and sets it up. + * @file: the file structure + * + * returns The allocated file or NULL on failure + */ +struct mei_cl *mei_cl_allocate(struct mei_device *dev) +{ + struct mei_cl *cl; + + cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); + if (!cl) + return NULL; + + mei_cl_init(cl, dev); + + return cl; +} + + + +/** + * mei_disconnect_host_client - sends disconnect message to fw from host client. + * + * @dev: the device structure + * @cl: private data of the file object + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) +{ + int rets, err; + long timeout = 15; /* 15 seconds */ + struct mei_cl_cb *cb; + + if (!dev || !cl) + return -ENODEV; + + if (cl->state != MEI_FILE_DISCONNECTING) + return 0; + + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) + return -ENOMEM; + + INIT_LIST_HEAD(&cb->cb_list); + cb->file_private = cl; + cb->major_file_operations = MEI_CLOSE; + if (dev->mei_host_buffer_is_empty) { + dev->mei_host_buffer_is_empty = false; + if (mei_disconnect(dev, cl)) { + rets = -ENODEV; + dev_dbg(&dev->pdev->dev, "failed to call mei_disconnect.\n"); + goto free; + } + mdelay(10); /* Wait for hardware disconnection ready */ + list_add_tail(&cb->cb_list, &dev->ctrl_rd_list.mei_cb.cb_list); + } else { + dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); + list_add_tail(&cb->cb_list, + &dev->ctrl_wr_list.mei_cb.cb_list); + } + mutex_unlock(&dev->device_lock); + + err = wait_event_timeout(dev->wait_recvd_msg, + (MEI_FILE_DISCONNECTED == cl->state), + timeout * HZ); + + mutex_lock(&dev->device_lock); + if (MEI_FILE_DISCONNECTED == cl->state) { + rets = 0; + dev_dbg(&dev->pdev->dev, "successfully disconnected from FW client.\n"); + } else { + rets = -ENODEV; + if (MEI_FILE_DISCONNECTED != cl->state) + dev_dbg(&dev->pdev->dev, "wrong status client disconnect.\n"); + + if (err) + dev_dbg(&dev->pdev->dev, + "wait failed disconnect err=%08x\n", + err); + + dev_dbg(&dev->pdev->dev, "failed to disconnect from FW client.\n"); + } + + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); +free: + mei_free_cb_private(cb); + return rets; +} + +/** + * mei_remove_client_from_file_list - + * removes file private data from device file list + * + * @dev: the device structure + * @host_client_id: host client id to be removed + */ +void mei_remove_client_from_file_list(struct mei_device *dev, + u8 host_client_id) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (host_client_id == cl_pos->host_client_id) { + dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + list_del_init(&cl_pos->link); + break; + } + } +} diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c new file mode 100644 index 000000000000..428d21e36416 --- /dev/null +++ b/drivers/misc/mei/interface.c @@ -0,0 +1,428 @@ +/* + * + * 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/pci.h> +#include "mei_dev.h" +#include <linux/mei.h> +#include "interface.h" + + + +/** + * mei_set_csr_register - writes H_CSR register to the mei device, + * and ignores the H_IS bit for it is write-one-to-zero. + * + * @dev: the device structure + */ +void mei_hcsr_set(struct mei_device *dev) +{ + if ((dev->host_hw_state & H_IS) == H_IS) + dev->host_hw_state &= ~H_IS; + mei_reg_write(dev, H_CSR, dev->host_hw_state); + dev->host_hw_state = mei_hcsr_read(dev); +} + +/** + * mei_csr_enable_interrupts - enables mei device interrupts + * + * @dev: the device structure + */ +void mei_enable_interrupts(struct mei_device *dev) +{ + dev->host_hw_state |= H_IE; + mei_hcsr_set(dev); +} + +/** + * mei_csr_disable_interrupts - disables mei device interrupts + * + * @dev: the device structure + */ +void mei_disable_interrupts(struct mei_device *dev) +{ + dev->host_hw_state &= ~H_IE; + mei_hcsr_set(dev); +} + +/** + * _host_get_filled_slots - gets number of device filled buffer slots + * + * @device: the device structure + * + * returns number of filled slots + */ +static unsigned char _host_get_filled_slots(const struct mei_device *dev) +{ + char read_ptr, write_ptr; + + read_ptr = (char) ((dev->host_hw_state & H_CBRP) >> 8); + write_ptr = (char) ((dev->host_hw_state & H_CBWP) >> 16); + + return (unsigned char) (write_ptr - read_ptr); +} + +/** + * mei_host_buffer_is_empty - checks if host buffer is empty. + * + * @dev: the device structure + * + * returns 1 if empty, 0 - otherwise. + */ +int mei_host_buffer_is_empty(struct mei_device *dev) +{ + unsigned char filled_slots; + + dev->host_hw_state = mei_hcsr_read(dev); + filled_slots = _host_get_filled_slots(dev); + + if (filled_slots == 0) + return 1; + + return 0; +} + +/** + * mei_count_empty_write_slots - counts write empty slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count + */ +int mei_count_empty_write_slots(struct mei_device *dev) +{ + unsigned char buffer_depth, filled_slots, empty_slots; + + dev->host_hw_state = mei_hcsr_read(dev); + buffer_depth = (unsigned char) ((dev->host_hw_state & H_CBD) >> 24); + filled_slots = _host_get_filled_slots(dev); + empty_slots = buffer_depth - filled_slots; + + /* check for overflow */ + if (filled_slots > buffer_depth) + return -EOVERFLOW; + + return empty_slots; +} + +/** + * mei_write_message - writes a message to mei device. + * + * @dev: the device structure + * @header: header of message + * @write_buffer: message buffer will be written + * @write_length: message size will be written + * + * This function returns -EIO if write has failed + */ +int mei_write_message(struct mei_device *dev, + struct mei_msg_hdr *header, + unsigned char *write_buffer, + unsigned long write_length) +{ + u32 temp_msg = 0; + unsigned long bytes_written = 0; + unsigned char buffer_depth, filled_slots, empty_slots; + unsigned long dw_to_write; + + dev->host_hw_state = mei_hcsr_read(dev); + + dev_dbg(&dev->pdev->dev, + "host_hw_state = 0x%08x.\n", + dev->host_hw_state); + + dev_dbg(&dev->pdev->dev, + "mei_write_message header=%08x.\n", + *((u32 *) header)); + + buffer_depth = (unsigned char) ((dev->host_hw_state & H_CBD) >> 24); + filled_slots = _host_get_filled_slots(dev); + empty_slots = buffer_depth - filled_slots; + dev_dbg(&dev->pdev->dev, + "filled = %hu, empty = %hu.\n", + filled_slots, empty_slots); + + dw_to_write = ((write_length + 3) / 4); + + if (dw_to_write > empty_slots) + return -EIO; + + mei_reg_write(dev, H_CB_WW, *((u32 *) header)); + + while (write_length >= 4) { + mei_reg_write(dev, H_CB_WW, + *(u32 *) (write_buffer + bytes_written)); + bytes_written += 4; + write_length -= 4; + } + + if (write_length > 0) { + memcpy(&temp_msg, &write_buffer[bytes_written], write_length); + mei_reg_write(dev, H_CB_WW, temp_msg); + } + + dev->host_hw_state |= H_IG; + mei_hcsr_set(dev); + dev->me_hw_state = mei_mecsr_read(dev); + if ((dev->me_hw_state & ME_RDY_HRA) != ME_RDY_HRA) + return -EIO; + + return 0; +} + +/** + * mei_count_full_read_slots - counts read full slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count + */ +int mei_count_full_read_slots(struct mei_device *dev) +{ + char read_ptr, write_ptr; + unsigned char buffer_depth, filled_slots; + + dev->me_hw_state = mei_mecsr_read(dev); + buffer_depth = (unsigned char)((dev->me_hw_state & ME_CBD_HRA) >> 24); + read_ptr = (char) ((dev->me_hw_state & ME_CBRP_HRA) >> 8); + write_ptr = (char) ((dev->me_hw_state & ME_CBWP_HRA) >> 16); + filled_slots = (unsigned char) (write_ptr - read_ptr); + + /* check for overflow */ + if (filled_slots > buffer_depth) + return -EOVERFLOW; + + dev_dbg(&dev->pdev->dev, "filled_slots =%08x\n", filled_slots); + return (int)filled_slots; +} + +/** + * mei_read_slots - reads a message from mei device. + * + * @dev: the device structure + * @buffer: message buffer will be written + * @buffer_length: message size will be read + */ +void mei_read_slots(struct mei_device *dev, unsigned char *buffer, + unsigned long buffer_length) +{ + u32 *reg_buf = (u32 *)buffer; + + for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) + *reg_buf++ = mei_mecbrw_read(dev); + + if (buffer_length > 0) { + u32 reg = mei_mecbrw_read(dev); + memcpy(reg_buf, ®, buffer_length); + } + + dev->host_hw_state |= H_IG; + mei_hcsr_set(dev); +} + +/** + * mei_flow_ctrl_creds - checks flow_control credentials. + * + * @dev: the device structure + * @cl: private data of the file object + * + * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. + * -ENOENT if mei_cl is not present + * -EINVAL if single_recv_buf == 0 + */ +int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl) +{ + int i; + + if (!dev->me_clients_num) + return 0; + + if (cl->mei_flow_ctrl_creds > 0) + return 1; + + for (i = 0; i < dev->me_clients_num; i++) { + struct mei_me_client *me_cl = &dev->me_clients[i]; + if (me_cl->client_id == cl->me_client_id) { + if (me_cl->mei_flow_ctrl_creds) { + if (WARN_ON(me_cl->props.single_recv_buf == 0)) + return -EINVAL; + return 1; + } else { + return 0; + } + } + } + return -ENOENT; +} + +/** + * mei_flow_ctrl_reduce - reduces flow_control. + * + * @dev: the device structure + * @cl: private data of the file object + * @returns + * 0 on success + * -ENOENT when me client is not found + * -EINVAL when ctrl credits are <= 0 + */ +int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl) +{ + int i; + + if (!dev->me_clients_num) + return -ENOENT; + + for (i = 0; i < dev->me_clients_num; i++) { + struct mei_me_client *me_cl = &dev->me_clients[i]; + if (me_cl->client_id == cl->me_client_id) { + if (me_cl->props.single_recv_buf != 0) { + if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + dev->me_clients[i].mei_flow_ctrl_creds--; + } else { + if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + cl->mei_flow_ctrl_creds--; + } + return 0; + } + } + return -ENOENT; +} + +/** + * mei_send_flow_control - sends flow control to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_flow_control *mei_flow_control; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_flow_control); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + mei_flow_control = (struct hbm_flow_control *) &dev->wr_msg_buf[1]; + memset(mei_flow_control, 0, sizeof(*mei_flow_control)); + mei_flow_control->host_addr = cl->host_client_id; + mei_flow_control->me_addr = cl->me_client_id; + mei_flow_control->hbm_cmd = MEI_FLOW_CONTROL_CMD; + memset(mei_flow_control->reserved, 0, + sizeof(mei_flow_control->reserved)); + dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", + cl->host_client_id, cl->me_client_id); + + return mei_write_message(dev, mei_hdr, + (unsigned char *) mei_flow_control, + sizeof(struct hbm_flow_control)); +} + +/** + * mei_other_client_is_connecting - checks if other + * client with the same client id is connected. + * + * @dev: the device structure + * @cl: private data of the file object + * + * returns 1 if other client is connected, 0 - otherwise. + */ +int mei_other_client_is_connecting(struct mei_device *dev, + struct mei_cl *cl) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if ((cl_pos->state == MEI_FILE_CONNECTING) && + (cl_pos != cl) && + cl->me_client_id == cl_pos->me_client_id) + return 1; + + } + return 0; +} + +/** + * mei_disconnect - sends disconnect message to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_client_disconnect_request *mei_cli_disconnect; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_client_disconnect_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + mei_cli_disconnect = + (struct hbm_client_disconnect_request *) &dev->wr_msg_buf[1]; + memset(mei_cli_disconnect, 0, sizeof(*mei_cli_disconnect)); + mei_cli_disconnect->host_addr = cl->host_client_id; + mei_cli_disconnect->me_addr = cl->me_client_id; + mei_cli_disconnect->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD; + mei_cli_disconnect->reserved[0] = 0; + + return mei_write_message(dev, mei_hdr, + (unsigned char *) mei_cli_disconnect, + sizeof(struct hbm_client_disconnect_request)); +} + +/** + * mei_connect - sends connect message to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_connect(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_client_connect_request *mei_cli_connect; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_client_connect_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + mei_cli_connect = + (struct hbm_client_connect_request *) &dev->wr_msg_buf[1]; + mei_cli_connect->host_addr = cl->host_client_id; + mei_cli_connect->me_addr = cl->me_client_id; + mei_cli_connect->hbm_cmd = CLIENT_CONNECT_REQ_CMD; + mei_cli_connect->reserved = 0; + + return mei_write_message(dev, mei_hdr, + (unsigned char *) mei_cli_connect, + sizeof(struct hbm_client_connect_request)); +} diff --git a/drivers/misc/mei/interface.h b/drivers/misc/mei/interface.h new file mode 100644 index 000000000000..ddff5d16616f --- /dev/null +++ b/drivers/misc/mei/interface.h @@ -0,0 +1,75 @@ +/* + * + * 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. + * + */ + + + +#ifndef _MEI_INTERFACE_H_ +#define _MEI_INTERFACE_H_ + +#include <linux/mei.h> +#include "mei_dev.h" + + +#define AMT_WD_DEFAULT_TIMEOUT 120 /* seconds */ +#define AMT_WD_MIN_TIMEOUT 120 /* seconds */ +#define AMT_WD_MAX_TIMEOUT 65535 /* seconds */ + +#define MEI_WATCHDOG_DATA_SIZE 16 +#define MEI_START_WD_DATA_SIZE 20 +#define MEI_WD_PARAMS_SIZE 4 + + +void mei_read_slots(struct mei_device *dev, + unsigned char *buffer, + unsigned long buffer_length); + +int mei_write_message(struct mei_device *dev, + struct mei_msg_hdr *header, + unsigned char *write_buffer, + unsigned long write_length); + +int mei_host_buffer_is_empty(struct mei_device *dev); + +int mei_count_full_read_slots(struct mei_device *dev); + +int mei_count_empty_write_slots(struct mei_device *dev); + +int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl); + +int mei_wd_send(struct mei_device *dev); +int mei_wd_stop(struct mei_device *dev, bool preserve); +int mei_wd_host_init(struct mei_device *dev); +/* + * mei_watchdog_register - Registering watchdog interface + * once we got connection to the WD Client + * @dev - mei device + */ +void mei_watchdog_register(struct mei_device *dev); +/* + * mei_watchdog_unregister - Unregistering watchdog interface + * @dev - mei device + */ +void mei_watchdog_unregister(struct mei_device *dev); + +int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl); + +int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl); + +int mei_disconnect(struct mei_device *dev, struct mei_cl *cl); +int mei_other_client_is_connecting(struct mei_device *dev, struct mei_cl *cl); +int mei_connect(struct mei_device *dev, struct mei_cl *cl); + +#endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c new file mode 100644 index 000000000000..93936f1b75eb --- /dev/null +++ b/drivers/misc/mei/interrupt.c @@ -0,0 +1,1590 @@ +/* + * + * 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/pci.h> +#include <linux/kthread.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/jiffies.h> + +#include "mei_dev.h" +#include <linux/mei.h> +#include "hw.h" +#include "interface.h" + + +/** + * mei_interrupt_quick_handler - The ISR of the MEI device + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + */ +irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + u32 csr_reg = mei_hcsr_read(dev); + + if ((csr_reg & H_IS) != H_IS) + return IRQ_NONE; + + /* clear H_IS bit in H_CSR */ + mei_reg_write(dev, H_CSR, csr_reg); + + return IRQ_WAKE_THREAD; +} + +/** + * _mei_cmpl - processes completed operation. + * + * @cl: private data of the file object. + * @cb_pos: callback block. + */ +static void _mei_cmpl(struct mei_cl *cl, struct mei_cl_cb *cb_pos) +{ + if (cb_pos->major_file_operations == MEI_WRITE) { + mei_free_cb_private(cb_pos); + cb_pos = NULL; + cl->writing_state = MEI_WRITE_COMPLETE; + if (waitqueue_active(&cl->tx_wait)) + wake_up_interruptible(&cl->tx_wait); + + } else if (cb_pos->major_file_operations == MEI_READ && + MEI_READING == cl->reading_state) { + cl->reading_state = MEI_READ_COMPLETE; + if (waitqueue_active(&cl->rx_wait)) + wake_up_interruptible(&cl->rx_wait); + + } +} + +/** + * _mei_cmpl_iamthif - processes completed iamthif operation. + * + * @dev: the device structure. + * @cb_pos: callback block. + */ +static void _mei_cmpl_iamthif(struct mei_device *dev, struct mei_cl_cb *cb_pos) +{ + if (dev->iamthif_canceled != 1) { + dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE; + dev->iamthif_stall_timer = 0; + memcpy(cb_pos->response_buffer.data, + dev->iamthif_msg_buf, + dev->iamthif_msg_buf_index); + list_add_tail(&cb_pos->cb_list, + &dev->amthi_read_complete_list.mei_cb.cb_list); + dev_dbg(&dev->pdev->dev, "amthi read completed.\n"); + dev->iamthif_timer = jiffies; + dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", + dev->iamthif_timer); + } else { + mei_run_next_iamthif_cmd(dev); + } + + dev_dbg(&dev->pdev->dev, "completing amthi call back.\n"); + wake_up_interruptible(&dev->iamthif_cl.wait); +} + + +/** + * mei_irq_thread_read_amthi_message - bottom half read routine after ISR to + * handle the read amthi message data processing. + * + * @complete_list: An instance of our list structure + * @dev: the device structure + * @mei_hdr: header of amthi message + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_read_amthi_message(struct mei_io_list *complete_list, + struct mei_device *dev, + struct mei_msg_hdr *mei_hdr) +{ + struct mei_cl *cl; + struct mei_cl_cb *cb; + unsigned char *buffer; + + BUG_ON(mei_hdr->me_addr != dev->iamthif_cl.me_client_id); + BUG_ON(dev->iamthif_state != MEI_IAMTHIF_READING); + + buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; + BUG_ON(dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length); + + mei_read_slots(dev, buffer, mei_hdr->length); + + dev->iamthif_msg_buf_index += mei_hdr->length; + + if (!mei_hdr->msg_complete) + return 0; + + dev_dbg(&dev->pdev->dev, + "amthi_message_buffer_index =%d\n", + mei_hdr->length); + + dev_dbg(&dev->pdev->dev, "completed amthi read.\n "); + if (!dev->iamthif_current_cb) + return -ENODEV; + + cb = dev->iamthif_current_cb; + dev->iamthif_current_cb = NULL; + + cl = (struct mei_cl *)cb->file_private; + if (!cl) + return -ENODEV; + + dev->iamthif_stall_timer = 0; + cb->information = dev->iamthif_msg_buf_index; + cb->read_time = jiffies; + if (dev->iamthif_ioctl && cl == &dev->iamthif_cl) { + /* found the iamthif cb */ + dev_dbg(&dev->pdev->dev, "complete the amthi read cb.\n "); + dev_dbg(&dev->pdev->dev, "add the amthi read cb to complete.\n "); + list_add_tail(&cb->cb_list, + &complete_list->mei_cb.cb_list); + } + return 0; +} + +/** + * _mei_irq_thread_state_ok - checks if mei header matches file private data + * + * @cl: private data of the file object + * @mei_hdr: header of mei client message + * + * returns !=0 if matches, 0 if no match. + */ +static int _mei_irq_thread_state_ok(struct mei_cl *cl, + struct mei_msg_hdr *mei_hdr) +{ + return (cl->host_client_id == mei_hdr->host_addr && + cl->me_client_id == mei_hdr->me_addr && + cl->state == MEI_FILE_CONNECTED && + MEI_READ_COMPLETE != cl->reading_state); +} + +/** + * mei_irq_thread_read_client_message - bottom half read routine after ISR to + * handle the read mei client message data processing. + * + * @complete_list: An instance of our list structure + * @dev: the device structure + * @mei_hdr: header of mei client message + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_read_client_message(struct mei_io_list *complete_list, + struct mei_device *dev, + struct mei_msg_hdr *mei_hdr) +{ + struct mei_cl *cl; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + unsigned char *buffer = NULL; + + dev_dbg(&dev->pdev->dev, "start client msg\n"); + if (list_empty(&dev->read_list.mei_cb.cb_list)) + goto quit; + + list_for_each_entry_safe(cb_pos, cb_next, + &dev->read_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)cb_pos->file_private; + if (cl && _mei_irq_thread_state_ok(cl, mei_hdr)) { + cl->reading_state = MEI_READING; + buffer = cb_pos->response_buffer.data + cb_pos->information; + + if (cb_pos->response_buffer.size < + mei_hdr->length + cb_pos->information) { + dev_dbg(&dev->pdev->dev, "message overflow.\n"); + list_del(&cb_pos->cb_list); + return -ENOMEM; + } + if (buffer) + mei_read_slots(dev, buffer, mei_hdr->length); + + cb_pos->information += mei_hdr->length; + if (mei_hdr->msg_complete) { + cl->status = 0; + list_del(&cb_pos->cb_list); + dev_dbg(&dev->pdev->dev, + "completed read host client = %d," + "ME client = %d, " + "data length = %lu\n", + cl->host_client_id, + cl->me_client_id, + cb_pos->information); + + *(cb_pos->response_buffer.data + + cb_pos->information) = '\0'; + dev_dbg(&dev->pdev->dev, "cb_pos->res_buffer - %s\n", + cb_pos->response_buffer.data); + list_add_tail(&cb_pos->cb_list, + &complete_list->mei_cb.cb_list); + } + + break; + } + + } + +quit: + dev_dbg(&dev->pdev->dev, "message read\n"); + if (!buffer) { + mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); + dev_dbg(&dev->pdev->dev, "discarding message, header =%08x.\n", + *(u32 *) dev->rd_msg_buf); + } + + return 0; +} + +/** + * _mei_irq_thread_iamthif_read - prepares to read iamthif data. + * + * @dev: the device structure. + * @slots: free slots. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_iamthif_read(struct mei_device *dev, s32 *slots) +{ + + if (((*slots) * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control))) { + return -EMSGSIZE; + } + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control) + 3) / 4; + if (mei_send_flow_control(dev, &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n"); + return -EIO; + } + + dev_dbg(&dev->pdev->dev, "iamthif flow control success\n"); + dev->iamthif_state = MEI_IAMTHIF_READING; + dev->iamthif_flow_control_pending = false; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_msg_buf_size = 0; + dev->iamthif_stall_timer = IAMTHIF_STALL_TIMER; + dev->mei_host_buffer_is_empty = mei_host_buffer_is_empty(dev); + return 0; +} + +/** + * _mei_irq_thread_close - processes close related operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_disconnect_request))) { + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_disconnect_request) + 3) / 4; + + if (mei_disconnect(dev, cl)) { + cl->status = 0; + cb_pos->information = 0; + list_move_tail(&cb_pos->cb_list, + &cmpl_list->mei_cb.cb_list); + return -EMSGSIZE; + } else { + cl->state = MEI_FILE_DISCONNECTING; + cl->status = 0; + cb_pos->information = 0; + list_move_tail(&cb_pos->cb_list, + &dev->ctrl_rd_list.mei_cb.cb_list); + cl->timer_count = MEI_CONNECT_TIMEOUT; + } + } else { + /* return the cancel routine */ + return -EBADMSG; + } + + return 0; +} + +/** + * is_treat_specially_client - checks if the message belongs + * to the file private data. + * + * @cl: private data of the file object + * @rs: connect response bus message + * + */ +static bool is_treat_specially_client(struct mei_cl *cl, + struct hbm_client_connect_response *rs) +{ + + if (cl->host_client_id == rs->host_addr && + cl->me_client_id == rs->me_addr) { + if (!rs->status) { + cl->state = MEI_FILE_CONNECTED; + cl->status = 0; + + } else { + cl->state = MEI_FILE_DISCONNECTED; + cl->status = -ENODEV; + } + cl->timer_count = 0; + + return true; + } + return false; +} + +/** + * mei_client_connect_response - connects to response irq routine + * + * @dev: the device structure + * @rs: connect response bus message + */ +static void mei_client_connect_response(struct mei_device *dev, + struct hbm_client_connect_response *rs) +{ + + struct mei_cl *cl; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + + dev_dbg(&dev->pdev->dev, + "connect_response:\n" + "ME Client = %d\n" + "Host Client = %d\n" + "Status = %d\n", + rs->me_addr, + rs->host_addr, + rs->status); + + /* if WD or iamthif client treat specially */ + + if (is_treat_specially_client(&(dev->wd_cl), rs)) { + dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n"); + mei_watchdog_register(dev); + + /* next step in the state maching */ + mei_host_init_iamthif(dev); + return; + } + + if (is_treat_specially_client(&(dev->iamthif_cl), rs)) { + dev->iamthif_state = MEI_IAMTHIF_IDLE; + return; + } + list_for_each_entry_safe(cb_pos, cb_next, + &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { + + cl = (struct mei_cl *)cb_pos->file_private; + if (!cl) { + list_del(&cb_pos->cb_list); + return; + } + if (MEI_IOCTL == cb_pos->major_file_operations) { + if (is_treat_specially_client(cl, rs)) { + list_del(&cb_pos->cb_list); + cl->status = 0; + cl->timer_count = 0; + break; + } + } + } +} + +/** + * mei_client_disconnect_response - disconnects from response irq routine + * + * @dev: the device structure + * @rs: disconnect response bus message + */ +static void mei_client_disconnect_response(struct mei_device *dev, + struct hbm_client_connect_response *rs) +{ + struct mei_cl *cl; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + + dev_dbg(&dev->pdev->dev, + "disconnect_response:\n" + "ME Client = %d\n" + "Host Client = %d\n" + "Status = %d\n", + rs->me_addr, + rs->host_addr, + rs->status); + + list_for_each_entry_safe(cb_pos, cb_next, + &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)cb_pos->file_private; + + if (!cl) { + list_del(&cb_pos->cb_list); + return; + } + + dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n"); + if (cl->host_client_id == rs->host_addr && + cl->me_client_id == rs->me_addr) { + + list_del(&cb_pos->cb_list); + if (!rs->status) + cl->state = MEI_FILE_DISCONNECTED; + + cl->status = 0; + cl->timer_count = 0; + break; + } + } +} + +/** + * same_flow_addr - tells if they have the same address. + * + * @file: private data of the file object. + * @flow: flow control. + * + * returns !=0, same; 0,not. + */ +static int same_flow_addr(struct mei_cl *cl, struct hbm_flow_control *flow) +{ + return (cl->host_client_id == flow->host_addr && + cl->me_client_id == flow->me_addr); +} + +/** + * add_single_flow_creds - adds single buffer credentials. + * + * @file: private data ot the file object. + * @flow: flow control. + */ +static void add_single_flow_creds(struct mei_device *dev, + struct hbm_flow_control *flow) +{ + struct mei_me_client *client; + int i; + + for (i = 0; i < dev->me_clients_num; i++) { + client = &dev->me_clients[i]; + if (client && flow->me_addr == client->client_id) { + if (client->props.single_recv_buf) { + client->mei_flow_ctrl_creds++; + dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", + flow->me_addr); + dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", + client->mei_flow_ctrl_creds); + } else { + BUG(); /* error in flow control */ + } + } + } +} + +/** + * mei_client_flow_control_response - flow control response irq routine + * + * @dev: the device structure + * @flow_control: flow control response bus message + */ +static void mei_client_flow_control_response(struct mei_device *dev, + struct hbm_flow_control *flow_control) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + + if (!flow_control->host_addr) { + /* single receive buffer */ + add_single_flow_creds(dev, flow_control); + } else { + /* normal connection */ + list_for_each_entry_safe(cl_pos, cl_next, + &dev->file_list, link) { + dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in file_list\n"); + + dev_dbg(&dev->pdev->dev, "cl of host client %d ME client %d.\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", + flow_control->host_addr, + flow_control->me_addr); + if (same_flow_addr(cl_pos, flow_control)) { + dev_dbg(&dev->pdev->dev, "recv ctrl msg for host %d ME %d.\n", + flow_control->host_addr, + flow_control->me_addr); + cl_pos->mei_flow_ctrl_creds++; + dev_dbg(&dev->pdev->dev, "flow control credentials = %d.\n", + cl_pos->mei_flow_ctrl_creds); + break; + } + } + } +} + +/** + * same_disconn_addr - tells if they have the same address + * + * @file: private data of the file object. + * @disconn: disconnection request. + * + * returns !=0, same; 0,not. + */ +static int same_disconn_addr(struct mei_cl *cl, + struct hbm_client_disconnect_request *disconn) +{ + return (cl->host_client_id == disconn->host_addr && + cl->me_client_id == disconn->me_addr); +} + +/** + * mei_client_disconnect_request - disconnects from request irq routine + * + * @dev: the device structure. + * @disconnect_req: disconnect request bus message. + */ +static void mei_client_disconnect_request(struct mei_device *dev, + struct hbm_client_disconnect_request *disconnect_req) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_client_connect_response *disconnect_res; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (same_disconn_addr(cl_pos, disconnect_req)) { + dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", + disconnect_req->host_addr, + disconnect_req->me_addr); + cl_pos->state = MEI_FILE_DISCONNECTED; + cl_pos->timer_count = 0; + if (cl_pos == &dev->wd_cl) { + dev->wd_due_counter = 0; + dev->wd_pending = false; + } else if (cl_pos == &dev->iamthif_cl) + dev->iamthif_timer = 0; + + /* prepare disconnect response */ + mei_hdr = + (struct mei_msg_hdr *) &dev->ext_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = + sizeof(struct hbm_client_connect_response); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + disconnect_res = + (struct hbm_client_connect_response *) + &dev->ext_msg_buf[1]; + disconnect_res->host_addr = cl_pos->host_client_id; + disconnect_res->me_addr = cl_pos->me_client_id; + disconnect_res->hbm_cmd = CLIENT_DISCONNECT_RES_CMD; + disconnect_res->status = 0; + dev->extra_write_index = 2; + break; + } + } +} + + +/** + * mei_irq_thread_read_bus_message - bottom half read routine after ISR to + * handle the read bus message cmd processing. + * + * @dev: the device structure + * @mei_hdr: header of bus message + */ +static void mei_irq_thread_read_bus_message(struct mei_device *dev, + struct mei_msg_hdr *mei_hdr) +{ + struct mei_bus_message *mei_msg; + struct hbm_host_version_response *version_res; + struct hbm_client_connect_response *connect_res; + struct hbm_client_connect_response *disconnect_res; + struct hbm_flow_control *flow_control; + struct hbm_props_response *props_res; + struct hbm_host_enum_response *enum_res; + struct hbm_client_disconnect_request *disconnect_req; + struct hbm_host_stop_request *host_stop_req; + int res; + + + /* read the message to our buffer */ + BUG_ON(mei_hdr->length >= sizeof(dev->rd_msg_buf)); + mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); + mei_msg = (struct mei_bus_message *)dev->rd_msg_buf; + + switch (mei_msg->hbm_cmd) { + case HOST_START_RES_CMD: + version_res = (struct hbm_host_version_response *) mei_msg; + if (version_res->host_version_supported) { + dev->version.major_version = HBM_MAJOR_VERSION; + dev->version.minor_version = HBM_MINOR_VERSION; + if (dev->mei_state == MEI_INIT_CLIENTS && + dev->init_clients_state == MEI_START_MESSAGE) { + dev->init_clients_timer = 0; + mei_host_enum_clients_message(dev); + } else { + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "IMEI reset due to received host start response bus message.\n"); + mei_reset(dev, 1); + return; + } + } else { + dev->version = version_res->me_max_version; + /* send stop message */ + mei_hdr = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_stop_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + host_stop_req = (struct hbm_host_stop_request *) + &dev->wr_msg_buf[1]; + + memset(host_stop_req, + 0, + sizeof(struct hbm_host_stop_request)); + host_stop_req->hbm_cmd = HOST_STOP_REQ_CMD; + host_stop_req->reason = DRIVER_STOP_REQUEST; + mei_write_message(dev, mei_hdr, + (unsigned char *) (host_stop_req), + mei_hdr->length); + dev_dbg(&dev->pdev->dev, "version mismatch.\n"); + return; + } + + dev->recvd_msg = true; + dev_dbg(&dev->pdev->dev, "host start response message received.\n"); + break; + + case CLIENT_CONNECT_RES_CMD: + connect_res = + (struct hbm_client_connect_response *) mei_msg; + mei_client_connect_response(dev, connect_res); + dev_dbg(&dev->pdev->dev, "client connect response message received.\n"); + wake_up(&dev->wait_recvd_msg); + break; + + case CLIENT_DISCONNECT_RES_CMD: + disconnect_res = + (struct hbm_client_connect_response *) mei_msg; + mei_client_disconnect_response(dev, disconnect_res); + dev_dbg(&dev->pdev->dev, "client disconnect response message received.\n"); + wake_up(&dev->wait_recvd_msg); + break; + + case MEI_FLOW_CONTROL_CMD: + flow_control = (struct hbm_flow_control *) mei_msg; + mei_client_flow_control_response(dev, flow_control); + dev_dbg(&dev->pdev->dev, "client flow control response message received.\n"); + break; + + case HOST_CLIENT_PROPERTIES_RES_CMD: + props_res = (struct hbm_props_response *)mei_msg; + if (props_res->status || !dev->me_clients) { + dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message wrong status.\n"); + mei_reset(dev, 1); + return; + } + if (dev->me_clients[dev->me_client_presentation_num] + .client_id == props_res->address) { + + dev->me_clients[dev->me_client_presentation_num].props + = props_res->client_properties; + + if (dev->mei_state == MEI_INIT_CLIENTS && + dev->init_clients_state == + MEI_CLIENT_PROPERTIES_MESSAGE) { + dev->me_client_index++; + dev->me_client_presentation_num++; + + /** Send Client Properties request **/ + res = mei_host_client_properties(dev); + if (res < 0) { + dev_dbg(&dev->pdev->dev, "mei_host_client_properties() failed"); + return; + } else if (!res) { + /* + * No more clients to send to. + * Clear Map for indicating now ME clients + * with associated host client + */ + bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); + dev->open_handle_count = 0; + + /* + * Reserving the first three client IDs + * Client Id 0 - Reserved for MEI Bus Message communications + * Client Id 1 - Reserved for Watchdog + * Client ID 2 - Reserved for AMTHI + */ + bitmap_set(dev->host_clients_map, 0, 3); + dev->mei_state = MEI_ENABLED; + + /* if wd initialization fails, initialization the AMTHI client, + * otherwise the AMTHI client will be initialized after the WD client connect response + * will be received + */ + if (mei_wd_host_init(dev)) + mei_host_init_iamthif(dev); + } + + } else { + dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message"); + mei_reset(dev, 1); + return; + } + } else { + dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message for wrong client ID\n"); + mei_reset(dev, 1); + return; + } + break; + + case HOST_ENUM_RES_CMD: + enum_res = (struct hbm_host_enum_response *) mei_msg; + memcpy(dev->me_clients_map, enum_res->valid_addresses, 32); + if (dev->mei_state == MEI_INIT_CLIENTS && + dev->init_clients_state == MEI_ENUM_CLIENTS_MESSAGE) { + dev->init_clients_timer = 0; + dev->me_client_presentation_num = 0; + dev->me_client_index = 0; + mei_allocate_me_clients_storage(dev); + dev->init_clients_state = + MEI_CLIENT_PROPERTIES_MESSAGE; + mei_host_client_properties(dev); + } else { + dev_dbg(&dev->pdev->dev, "reset due to received host enumeration clients response bus message.\n"); + mei_reset(dev, 1); + return; + } + break; + + case HOST_STOP_RES_CMD: + dev->mei_state = MEI_DISABLED; + dev_dbg(&dev->pdev->dev, "resetting because of FW stop response.\n"); + mei_reset(dev, 1); + break; + + case CLIENT_DISCONNECT_REQ_CMD: + /* search for client */ + disconnect_req = + (struct hbm_client_disconnect_request *) mei_msg; + mei_client_disconnect_request(dev, disconnect_req); + break; + + case ME_STOP_REQ_CMD: + /* prepare stop request */ + mei_hdr = (struct mei_msg_hdr *) &dev->ext_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_stop_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + host_stop_req = + (struct hbm_host_stop_request *) &dev->ext_msg_buf[1]; + memset(host_stop_req, 0, sizeof(struct hbm_host_stop_request)); + host_stop_req->hbm_cmd = HOST_STOP_REQ_CMD; + host_stop_req->reason = DRIVER_STOP_REQUEST; + host_stop_req->reserved[0] = 0; + host_stop_req->reserved[1] = 0; + dev->extra_write_index = 2; + break; + + default: + BUG(); + break; + + } +} + + +/** + * _mei_hb_read - processes read related operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control))) { + /* return the cancel routine */ + list_del(&cb_pos->cb_list); + return -EBADMSG; + } + + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control) + 3) / 4; + if (mei_send_flow_control(dev, cl)) { + cl->status = -ENODEV; + cb_pos->information = 0; + list_move_tail(&cb_pos->cb_list, &cmpl_list->mei_cb.cb_list); + return -ENODEV; + } + list_move_tail(&cb_pos->cb_list, &dev->read_list.mei_cb.cb_list); + + return 0; +} + + +/** + * _mei_irq_thread_ioctl - processes ioctl related operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_connect_request))) { + cl->state = MEI_FILE_CONNECTING; + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_connect_request) + 3) / 4; + if (mei_connect(dev, cl)) { + cl->status = -ENODEV; + cb_pos->information = 0; + list_del(&cb_pos->cb_list); + return -ENODEV; + } else { + list_move_tail(&cb_pos->cb_list, + &dev->ctrl_rd_list.mei_cb.cb_list); + cl->timer_count = MEI_CONNECT_TIMEOUT; + } + } else { + /* return the cancel routine */ + list_del(&cb_pos->cb_list); + return -EBADMSG; + } + + return 0; +} + +/** + * _mei_irq_thread_cmpl - processes completed and no-iamthif operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_cmpl(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + struct mei_msg_hdr *mei_hdr; + + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + (cb_pos->request_buffer.size - + cb_pos->information))) { + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = cb_pos->request_buffer.size - + cb_pos->information; + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + dev_dbg(&dev->pdev->dev, "cb_pos->request_buffer.size =%d" + "mei_hdr->msg_complete = %d\n", + cb_pos->request_buffer.size, + mei_hdr->msg_complete); + dev_dbg(&dev->pdev->dev, "cb_pos->information =%lu\n", + cb_pos->information); + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", + mei_hdr->length); + *slots -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + if (mei_write_message(dev, mei_hdr, + (unsigned char *) + (cb_pos->request_buffer.data + + cb_pos->information), + mei_hdr->length)) { + cl->status = -ENODEV; + list_move_tail(&cb_pos->cb_list, + &cmpl_list->mei_cb.cb_list); + return -ENODEV; + } else { + if (mei_flow_ctrl_reduce(dev, cl)) + return -ENODEV; + cl->status = 0; + cb_pos->information += mei_hdr->length; + list_move_tail(&cb_pos->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + } + } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { + /* buffer is still empty */ + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = + (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + mei_hdr->msg_complete = 0; + mei_hdr->reserved = 0; + + (*slots) -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + if (mei_write_message(dev, mei_hdr, + (unsigned char *) + (cb_pos->request_buffer.data + + cb_pos->information), + mei_hdr->length)) { + cl->status = -ENODEV; + list_move_tail(&cb_pos->cb_list, + &cmpl_list->mei_cb.cb_list); + return -ENODEV; + } else { + cb_pos->information += mei_hdr->length; + dev_dbg(&dev->pdev->dev, + "cb_pos->request_buffer.size =%d" + " mei_hdr->msg_complete = %d\n", + cb_pos->request_buffer.size, + mei_hdr->msg_complete); + dev_dbg(&dev->pdev->dev, "cb_pos->information =%lu\n", + cb_pos->information); + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", + mei_hdr->length); + } + return -EMSGSIZE; + } else { + return -EBADMSG; + } + + return 0; +} + +/** + * _mei_irq_thread_cmpl_iamthif - processes completed iamthif operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_cmpl_iamthif(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + struct mei_msg_hdr *mei_hdr; + + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + dev->iamthif_msg_buf_size - + dev->iamthif_msg_buf_index)) { + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = dev->iamthif_msg_buf_size - + dev->iamthif_msg_buf_index; + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + *slots -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + + if (mei_write_message(dev, mei_hdr, + (dev->iamthif_msg_buf + + dev->iamthif_msg_buf_index), + mei_hdr->length)) { + dev->iamthif_state = MEI_IAMTHIF_IDLE; + cl->status = -ENODEV; + list_del(&cb_pos->cb_list); + return -ENODEV; + } else { + if (mei_flow_ctrl_reduce(dev, cl)) + return -ENODEV; + dev->iamthif_msg_buf_index += mei_hdr->length; + cb_pos->information = dev->iamthif_msg_buf_index; + cl->status = 0; + dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; + dev->iamthif_flow_control_pending = true; + /* save iamthif cb sent to amthi client */ + dev->iamthif_current_cb = cb_pos; + list_move_tail(&cb_pos->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + + } + } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { + /* buffer is still empty */ + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = + (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + mei_hdr->msg_complete = 0; + mei_hdr->reserved = 0; + + *slots -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + + if (mei_write_message(dev, mei_hdr, + (dev->iamthif_msg_buf + + dev->iamthif_msg_buf_index), + mei_hdr->length)) { + cl->status = -ENODEV; + list_del(&cb_pos->cb_list); + } else { + dev->iamthif_msg_buf_index += mei_hdr->length; + } + return -EMSGSIZE; + } else { + return -EBADMSG; + } + + return 0; +} + +/** + * mei_irq_thread_read_handler - bottom half read routine after ISR to + * handle the read processing. + * + * @cmpl_list: An instance of our list structure + * @dev: the device structure + * @slots: slots to read. + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_read_handler(struct mei_io_list *cmpl_list, + struct mei_device *dev, + s32 *slots) +{ + struct mei_msg_hdr *mei_hdr; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + int ret = 0; + + if (!dev->rd_msg_hdr) { + dev->rd_msg_hdr = mei_mecbrw_read(dev); + dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); + (*slots)--; + dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); + } + mei_hdr = (struct mei_msg_hdr *) &dev->rd_msg_hdr; + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", mei_hdr->length); + + if (mei_hdr->reserved || !dev->rd_msg_hdr) { + dev_dbg(&dev->pdev->dev, "corrupted message header.\n"); + ret = -EBADMSG; + goto end; + } + + if (mei_hdr->host_addr || mei_hdr->me_addr) { + list_for_each_entry_safe(cl_pos, cl_next, + &dev->file_list, link) { + dev_dbg(&dev->pdev->dev, + "list_for_each_entry_safe read host" + " client = %d, ME client = %d\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + if (cl_pos->host_client_id == mei_hdr->host_addr && + cl_pos->me_client_id == mei_hdr->me_addr) + break; + } + + if (&cl_pos->link == &dev->file_list) { + dev_dbg(&dev->pdev->dev, "corrupted message header\n"); + ret = -EBADMSG; + goto end; + } + } + if (((*slots) * sizeof(u32)) < mei_hdr->length) { + dev_dbg(&dev->pdev->dev, + "we can't read the message slots =%08x.\n", + *slots); + /* we can't read the message */ + ret = -ERANGE; + goto end; + } + + /* decide where to read the message too */ + if (!mei_hdr->host_addr) { + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_bus_message.\n"); + mei_irq_thread_read_bus_message(dev, mei_hdr); + dev_dbg(&dev->pdev->dev, "end mei_irq_thread_read_bus_message.\n"); + } else if (mei_hdr->host_addr == dev->iamthif_cl.host_client_id && + (MEI_FILE_CONNECTED == dev->iamthif_cl.state) && + (dev->iamthif_state == MEI_IAMTHIF_READING)) { + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n"); + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", + mei_hdr->length); + ret = mei_irq_thread_read_amthi_message(cmpl_list, + dev, mei_hdr); + if (ret) + goto end; + + } else { + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_client_message.\n"); + ret = mei_irq_thread_read_client_message(cmpl_list, + dev, mei_hdr); + if (ret) + goto end; + + } + + /* reset the number of slots and header */ + *slots = mei_count_full_read_slots(dev); + dev->rd_msg_hdr = 0; + + if (*slots == -EOVERFLOW) { + /* overflow - reset */ + dev_dbg(&dev->pdev->dev, "resetting due to slots overflow.\n"); + /* set the event since message has been read */ + ret = -ERANGE; + goto end; + } +end: + return ret; +} + + +/** + * mei_irq_thread_write_handler - bottom half write routine after + * ISR to handle the write processing. + * + * @cmpl_list: An instance of our list structure + * @dev: the device structure + * @slots: slots to write. + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, + struct mei_device *dev, + s32 *slots) +{ + + struct mei_cl *cl; + struct mei_cl_cb *pos = NULL, *next = NULL; + struct mei_io_list *list; + int ret; + + if (!mei_host_buffer_is_empty(dev)) { + dev_dbg(&dev->pdev->dev, "host buffer is not empty.\n"); + return 0; + } + *slots = mei_count_empty_write_slots(dev); + /* complete all waiting for write CB */ + dev_dbg(&dev->pdev->dev, "complete all waiting for write cb.\n"); + + list = &dev->write_waiting_list; + list_for_each_entry_safe(pos, next, + &list->mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)pos->file_private; + if (cl == NULL) + continue; + + cl->status = 0; + list_del(&pos->cb_list); + if (MEI_WRITING == cl->writing_state && + (pos->major_file_operations == MEI_WRITE) && + (cl != &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, + "MEI WRITE COMPLETE\n"); + cl->writing_state = MEI_WRITE_COMPLETE; + list_add_tail(&pos->cb_list, + &cmpl_list->mei_cb.cb_list); + } + if (cl == &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "check iamthif flow control.\n"); + if (dev->iamthif_flow_control_pending) { + ret = _mei_irq_thread_iamthif_read( + dev, slots); + if (ret) + return ret; + } + } + } + + if (dev->stop && !dev->wd_pending) { + dev->wd_stopped = true; + wake_up_interruptible(&dev->wait_stop_wd); + return 0; + } + + if (dev->extra_write_index) { + dev_dbg(&dev->pdev->dev, "extra_write_index =%d.\n", + dev->extra_write_index); + mei_write_message(dev, + (struct mei_msg_hdr *) &dev->ext_msg_buf[0], + (unsigned char *) &dev->ext_msg_buf[1], + (dev->extra_write_index - 1) * sizeof(u32)); + *slots -= dev->extra_write_index; + dev->extra_write_index = 0; + } + if (dev->mei_state == MEI_ENABLED) { + if (dev->wd_pending && + mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { + if (mei_wd_send(dev)) + dev_dbg(&dev->pdev->dev, "wd send failed.\n"); + else + if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) + return -ENODEV; + + dev->wd_pending = false; + + if (dev->wd_timeout) { + *slots -= (sizeof(struct mei_msg_hdr) + + MEI_START_WD_DATA_SIZE + 3) / 4; + dev->wd_due_counter = 2; + } else { + *slots -= (sizeof(struct mei_msg_hdr) + + MEI_WD_PARAMS_SIZE + 3) / 4; + dev->wd_due_counter = 0; + } + + } + } + if (dev->stop) + return -ENODEV; + + /* complete control write list CB */ + dev_dbg(&dev->pdev->dev, "complete control write list cb.\n"); + list_for_each_entry_safe(pos, next, + &dev->ctrl_wr_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *) pos->file_private; + if (!cl) { + list_del(&pos->cb_list); + return -ENODEV; + } + switch (pos->major_file_operations) { + case MEI_CLOSE: + /* send disconnect message */ + ret = _mei_irq_thread_close(dev, slots, pos, cl, cmpl_list); + if (ret) + return ret; + + break; + case MEI_READ: + /* send flow control message */ + ret = _mei_irq_thread_read(dev, slots, pos, cl, cmpl_list); + if (ret) + return ret; + + break; + case MEI_IOCTL: + /* connect message */ + if (mei_other_client_is_connecting(dev, cl)) + continue; + ret = _mei_irq_thread_ioctl(dev, slots, pos, cl, cmpl_list); + if (ret) + return ret; + + break; + + default: + BUG(); + } + + } + /* complete write list CB */ + dev_dbg(&dev->pdev->dev, "complete write list cb.\n"); + list_for_each_entry_safe(pos, next, + &dev->write_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)pos->file_private; + if (cl == NULL) + continue; + + if (cl != &dev->iamthif_cl) { + if (!mei_flow_ctrl_creds(dev, cl)) { + dev_dbg(&dev->pdev->dev, + "No flow control" + " credentials for client" + " %d, not sending.\n", + cl->host_client_id); + continue; + } + ret = _mei_irq_thread_cmpl(dev, slots, + pos, + cl, cmpl_list); + if (ret) + return ret; + + } else if (cl == &dev->iamthif_cl) { + /* IAMTHIF IOCTL */ + dev_dbg(&dev->pdev->dev, "complete amthi write cb.\n"); + if (!mei_flow_ctrl_creds(dev, cl)) { + dev_dbg(&dev->pdev->dev, + "No flow control" + " credentials for amthi" + " client %d.\n", + cl->host_client_id); + continue; + } + ret = _mei_irq_thread_cmpl_iamthif(dev, + slots, + pos, + cl, + cmpl_list); + if (ret) + return ret; + + } + + } + return 0; +} + + + +/** + * mei_timer - timer function. + * + * @work: pointer to the work_struct structure + * + * NOTE: This function is called by timer interrupt work + */ +void mei_timer(struct work_struct *work) +{ + unsigned long timeout; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + struct list_head *amthi_complete_list = NULL; + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb_next = NULL; + + struct mei_device *dev = container_of(work, + struct mei_device, timer_work.work); + + + mutex_lock(&dev->device_lock); + if (dev->mei_state != MEI_ENABLED) { + if (dev->mei_state == MEI_INIT_CLIENTS) { + if (dev->init_clients_timer) { + if (--dev->init_clients_timer == 0) { + dev_dbg(&dev->pdev->dev, "IMEI reset due to init clients timeout ,init clients state = %d.\n", + dev->init_clients_state); + mei_reset(dev, 1); + } + } + } + goto out; + } + /*** connect/disconnect timeouts ***/ + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (cl_pos->timer_count) { + if (--cl_pos->timer_count == 0) { + dev_dbg(&dev->pdev->dev, "HECI reset due to connect/disconnect timeout.\n"); + mei_reset(dev, 1); + goto out; + } + } + } + + if (dev->iamthif_stall_timer) { + if (--dev->iamthif_stall_timer == 0) { + dev_dbg(&dev->pdev->dev, "resetting because of hang to amthi.\n"); + mei_reset(dev, 1); + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; + + if (dev->iamthif_current_cb) + mei_free_cb_private(dev->iamthif_current_cb); + + dev->iamthif_file_object = NULL; + dev->iamthif_current_cb = NULL; + mei_run_next_iamthif_cmd(dev); + } + } + + if (dev->iamthif_timer) { + + timeout = dev->iamthif_timer + + msecs_to_jiffies(IAMTHIF_READ_TIMER); + + dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", + dev->iamthif_timer); + dev_dbg(&dev->pdev->dev, "timeout = %ld\n", timeout); + dev_dbg(&dev->pdev->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->pdev->dev, "freeing AMTHI for other requests\n"); + + amthi_complete_list = &dev->amthi_read_complete_list. + mei_cb.cb_list; + + list_for_each_entry_safe(cb_pos, cb_next, amthi_complete_list, cb_list) { + + cl_pos = cb_pos->file_object->private_data; + + /* Finding the AMTHI entry. */ + if (cl_pos == &dev->iamthif_cl) + list_del(&cb_pos->cb_list); + } + if (dev->iamthif_current_cb) + mei_free_cb_private(dev->iamthif_current_cb); + + dev->iamthif_file_object->private_data = NULL; + dev->iamthif_file_object = NULL; + dev->iamthif_current_cb = NULL; + dev->iamthif_timer = 0; + mei_run_next_iamthif_cmd(dev); + + } + } +out: + schedule_delayed_work(&dev->timer_work, 2 * HZ); + mutex_unlock(&dev->device_lock); +} + +/** + * mei_interrupt_thread_handler - function called after ISR to handle the interrupt + * processing. + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + * + */ +irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + struct mei_io_list complete_list; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + struct mei_cl *cl; + s32 slots; + int rets; + bool bus_message_received; + + + dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); + /* initialize our complete list */ + mutex_lock(&dev->device_lock); + mei_io_list_init(&complete_list); + dev->host_hw_state = mei_hcsr_read(dev); + + /* Ack the interrupt here + * In case of MSI we don't go through the quick handler */ + if (pci_dev_msi_enabled(dev->pdev)) + mei_reg_write(dev, H_CSR, dev->host_hw_state); + + dev->me_hw_state = mei_mecsr_read(dev); + + /* check if ME wants a reset */ + if ((dev->me_hw_state & ME_RDY_HRA) == 0 && + dev->mei_state != MEI_RESETING && + dev->mei_state != MEI_INITIALIZING) { + dev_dbg(&dev->pdev->dev, "FW not ready.\n"); + mei_reset(dev, 1); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } + + /* check if we need to start the dev */ + if ((dev->host_hw_state & H_RDY) == 0) { + if ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA) { + dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); + dev->host_hw_state |= (H_IE | H_IG | H_RDY); + mei_hcsr_set(dev); + dev->mei_state = MEI_INIT_CLIENTS; + dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); + /* link is established + * start sending messages. + */ + mei_host_start_message(dev); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } else { + dev_dbg(&dev->pdev->dev, "FW not ready.\n"); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } + } + /* check slots available for reading */ + slots = mei_count_full_read_slots(dev); + dev_dbg(&dev->pdev->dev, "slots =%08x extra_write_index =%08x.\n", + slots, dev->extra_write_index); + while (slots > 0 && !dev->extra_write_index) { + dev_dbg(&dev->pdev->dev, "slots =%08x extra_write_index =%08x.\n", + slots, dev->extra_write_index); + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_handler.\n"); + rets = mei_irq_thread_read_handler(&complete_list, dev, &slots); + if (rets) + goto end; + } + rets = mei_irq_thread_write_handler(&complete_list, dev, &slots); +end: + dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); + dev->host_hw_state = mei_hcsr_read(dev); + dev->mei_host_buffer_is_empty = mei_host_buffer_is_empty(dev); + + bus_message_received = false; + if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { + dev_dbg(&dev->pdev->dev, "received waiting bus message\n"); + bus_message_received = true; + } + mutex_unlock(&dev->device_lock); + if (bus_message_received) { + dev_dbg(&dev->pdev->dev, "wake up dev->wait_recvd_msg\n"); + wake_up_interruptible(&dev->wait_recvd_msg); + bus_message_received = false; + } + if (list_empty(&complete_list.mei_cb.cb_list)) + return IRQ_HANDLED; + + + list_for_each_entry_safe(cb_pos, cb_next, + &complete_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)cb_pos->file_private; + list_del(&cb_pos->cb_list); + if (cl) { + if (cl != &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "completing call back.\n"); + _mei_cmpl(cl, cb_pos); + cb_pos = NULL; + } else if (cl == &dev->iamthif_cl) { + _mei_cmpl_iamthif(dev, cb_pos); + } + } + } + return IRQ_HANDLED; +} diff --git a/drivers/misc/mei/iorw.c b/drivers/misc/mei/iorw.c new file mode 100644 index 000000000000..f9cced69b65e --- /dev/null +++ b/drivers/misc/mei/iorw.c @@ -0,0 +1,590 @@ +/* + * + * 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/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/aio.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/cdev.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/uuid.h> +#include <linux/jiffies.h> +#include <linux/uaccess.h> + + +#include "mei_dev.h" +#include "hw.h" +#include <linux/mei.h> +#include "interface.h" + + + +/** + * mei_ioctl_connect_client - the connect to fw client IOCTL function + * + * @dev: the device structure + * @data: IOCTL connect data, input and output parameters + * @file: private data of the file object + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +int mei_ioctl_connect_client(struct file *file, + struct mei_connect_client_data *data) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + struct mei_client *client; + struct mei_cl *cl; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + long timeout = CONNECT_TIMEOUT; + int i; + int err; + int rets; + + cl = file->private_data; + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + dev_dbg(&dev->pdev->dev, "mei_ioctl_connect_client() Entry\n"); + + + /* buffered ioctl cb */ + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) { + rets = -ENOMEM; + goto end; + } + INIT_LIST_HEAD(&cb->cb_list); + + cb->major_file_operations = MEI_IOCTL; + + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto end; + } + if (cl->state != MEI_FILE_INITIALIZING && + cl->state != MEI_FILE_DISCONNECTED) { + rets = -EBUSY; + goto end; + } + + /* find ME client we're trying to connect to */ + i = mei_find_me_client_index(dev, data->in_client_uuid); + if (i >= 0 && !dev->me_clients[i].props.fixed_address) { + cl->me_client_id = dev->me_clients[i].client_id; + cl->state = MEI_FILE_CONNECTING; + } + + dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", + cl->me_client_id); + dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n", + dev->me_clients[i].props.protocol_version); + dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", + dev->me_clients[i].props.max_msg_length); + + /* if we're connecting to amthi client then we will use the + * existing connection + */ + if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { + dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); + if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + goto end; + } + clear_bit(cl->host_client_id, dev->host_clients_map); + list_for_each_entry_safe(cl_pos, cl_next, + &dev->file_list, link) { + if (mei_cl_cmp_id(cl, cl_pos)) { + dev_dbg(&dev->pdev->dev, + "remove file private data node host" + " client = %d, ME client = %d.\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + list_del(&cl_pos->link); + } + + } + dev_dbg(&dev->pdev->dev, "free file private data memory.\n"); + kfree(cl); + + cl = NULL; + file->private_data = &dev->iamthif_cl; + + client = &data->out_client_properties; + client->max_msg_length = + dev->me_clients[i].props.max_msg_length; + client->protocol_version = + dev->me_clients[i].props.protocol_version; + rets = dev->iamthif_cl.status; + + goto end; + } + + if (cl->state != MEI_FILE_CONNECTING) { + rets = -ENODEV; + goto end; + } + + + /* prepare the output buffer */ + client = &data->out_client_properties; + client->max_msg_length = dev->me_clients[i].props.max_msg_length; + client->protocol_version = dev->me_clients[i].props.protocol_version; + dev_dbg(&dev->pdev->dev, "Can connect?\n"); + if (dev->mei_host_buffer_is_empty + && !mei_other_client_is_connecting(dev, cl)) { + dev_dbg(&dev->pdev->dev, "Sending Connect Message\n"); + dev->mei_host_buffer_is_empty = false; + if (mei_connect(dev, cl)) { + dev_dbg(&dev->pdev->dev, "Sending connect message - failed\n"); + rets = -ENODEV; + goto end; + } else { + dev_dbg(&dev->pdev->dev, "Sending connect message - succeeded\n"); + cl->timer_count = MEI_CONNECT_TIMEOUT; + cb->file_private = cl; + list_add_tail(&cb->cb_list, + &dev->ctrl_rd_list.mei_cb. + cb_list); + } + + + } else { + dev_dbg(&dev->pdev->dev, "Queuing the connect request due to device busy\n"); + cb->file_private = cl; + dev_dbg(&dev->pdev->dev, "add connect cb to control write list.\n"); + list_add_tail(&cb->cb_list, + &dev->ctrl_wr_list.mei_cb.cb_list); + } + mutex_unlock(&dev->device_lock); + err = wait_event_timeout(dev->wait_recvd_msg, + (MEI_FILE_CONNECTED == cl->state || + MEI_FILE_DISCONNECTED == cl->state), + timeout * HZ); + + mutex_lock(&dev->device_lock); + if (MEI_FILE_CONNECTED == cl->state) { + dev_dbg(&dev->pdev->dev, "successfully connected to FW client.\n"); + rets = cl->status; + goto end; + } else { + dev_dbg(&dev->pdev->dev, "failed to connect to FW client.cl->state = %d.\n", + cl->state); + if (!err) { + dev_dbg(&dev->pdev->dev, + "wait_event_interruptible_timeout failed on client" + " connect message fw response message.\n"); + } + rets = -EFAULT; + + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); + goto end; + } + rets = 0; +end: + dev_dbg(&dev->pdev->dev, "free connect cb memory."); + kfree(cb); + return rets; +} + +/** + * find_amthi_read_list_entry - finds a amthilist entry for current file + * + * @dev: the device structure + * @file: pointer to file object + * + * returns returned a list entry on success, NULL on failure. + */ +struct mei_cl_cb *find_amthi_read_list_entry( + struct mei_device *dev, + struct file *file) +{ + struct mei_cl *cl_temp; + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + + list_for_each_entry_safe(pos, next, + &dev->amthi_read_complete_list.mei_cb.cb_list, cb_list) { + cl_temp = (struct mei_cl *)pos->file_private; + if (cl_temp && cl_temp == &dev->iamthif_cl && + pos->file_object == file) + return pos; + } + return NULL; +} + +/** + * amthi_read - read data from AMTHI client + * + * @dev: the device structure + * @if_num: minor number + * @file: pointer to file object + * @*ubuf: pointer to user data in user space + * @length: data length to read + * @offset: data read offset + * + * Locking: called under "dev->device_lock" lock + * + * returns + * returned data length on success, + * zero if no data to read, + * negative on failure. + */ +int amthi_read(struct mei_device *dev, struct file *file, + char __user *ubuf, size_t length, loff_t *offset) +{ + int rets; + int wait_ret; + struct mei_cl_cb *cb = NULL; + struct mei_cl *cl = file->private_data; + unsigned long timeout; + int i; + + /* Only Posible if we are in timeout */ + if (!cl || cl != &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "bad file ext.\n"); + return -ETIMEDOUT; + } + + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == + dev->iamthif_cl.me_client_id) + break; + } + + if (i == dev->me_clients_num) { + dev_dbg(&dev->pdev->dev, "amthi client not found.\n"); + return -ENODEV; + } + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) + return -ENODEV; + + dev_dbg(&dev->pdev->dev, "checking amthi data\n"); + cb = find_amthi_read_list_entry(dev, file); + + /* Check for if we can block or not*/ + if (cb == NULL && file->f_flags & O_NONBLOCK) + return -EAGAIN; + + + dev_dbg(&dev->pdev->dev, "waiting for amthi data\n"); + while (cb == NULL) { + /* unlock the Mutex */ + mutex_unlock(&dev->device_lock); + + wait_ret = wait_event_interruptible(dev->iamthif_cl.wait, + (cb = find_amthi_read_list_entry(dev, file))); + + if (wait_ret) + return -ERESTARTSYS; + + dev_dbg(&dev->pdev->dev, "woke up from sleep\n"); + + /* Locking again the Mutex */ + mutex_lock(&dev->device_lock); + } + + + dev_dbg(&dev->pdev->dev, "Got amthi data\n"); + dev->iamthif_timer = 0; + + if (cb) { + timeout = cb->read_time + + msecs_to_jiffies(IAMTHIF_READ_TIMER); + dev_dbg(&dev->pdev->dev, "amthi timeout = %lud\n", + timeout); + + if (time_after(jiffies, timeout)) { + dev_dbg(&dev->pdev->dev, "amthi Time out\n"); + /* 15 sec for the message has expired */ + list_del(&cb->cb_list); + rets = -ETIMEDOUT; + goto free; + } + } + /* if the whole message will fit remove it from the list */ + if (cb->information >= *offset && length >= (cb->information - *offset)) + list_del(&cb->cb_list); + else if (cb->information > 0 && cb->information <= *offset) { + /* end of the message has been reached */ + list_del(&cb->cb_list); + rets = 0; + goto free; + } + /* else means that not full buffer will be read and do not + * remove message from deletion list + */ + + dev_dbg(&dev->pdev->dev, "amthi cb->response_buffer size - %d\n", + cb->response_buffer.size); + dev_dbg(&dev->pdev->dev, "amthi cb->information - %lu\n", + cb->information); + + /* length is being turncated to PAGE_SIZE, however, + * the information may be longer */ + length = min_t(size_t, length, (cb->information - *offset)); + + if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) + rets = -EFAULT; + else { + rets = length; + if ((*offset + length) < cb->information) { + *offset += length; + goto out; + } + } +free: + dev_dbg(&dev->pdev->dev, "free amthi cb memory.\n"); + *offset = 0; + mei_free_cb_private(cb); +out: + return rets; +} + +/** + * mei_start_read - the start read client message function. + * + * @dev: the device structure + * @if_num: minor number + * @cl: private data of the file object + * + * returns 0 on success, <0 on failure. + */ +int mei_start_read(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_cl_cb *cb; + int rets = 0; + int i; + + if (cl->state != MEI_FILE_CONNECTED) + return -ENODEV; + + if (dev->mei_state != MEI_ENABLED) + return -ENODEV; + + dev_dbg(&dev->pdev->dev, "check if read is pending.\n"); + if (cl->read_pending || cl->read_cb) { + dev_dbg(&dev->pdev->dev, "read is pending.\n"); + return -EBUSY; + } + + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) + return -ENOMEM; + + dev_dbg(&dev->pdev->dev, "allocation call back successful. host client = %d, ME client = %d\n", + cl->host_client_id, cl->me_client_id); + + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == cl->me_client_id) + break; + + } + + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) { + rets = -ENODEV; + goto unlock; + } + + if (i == dev->me_clients_num) { + rets = -ENODEV; + goto unlock; + } + + cb->response_buffer.size = dev->me_clients[i].props.max_msg_length; + cb->response_buffer.data = + kmalloc(cb->response_buffer.size, GFP_KERNEL); + if (!cb->response_buffer.data) { + rets = -ENOMEM; + goto unlock; + } + dev_dbg(&dev->pdev->dev, "allocation call back data success.\n"); + cb->major_file_operations = MEI_READ; + /* make sure information is zero before we start */ + cb->information = 0; + cb->file_private = (void *) cl; + cl->read_cb = cb; + if (dev->mei_host_buffer_is_empty) { + dev->mei_host_buffer_is_empty = false; + if (mei_send_flow_control(dev, cl)) { + rets = -ENODEV; + goto unlock; + } + list_add_tail(&cb->cb_list, &dev->read_list.mei_cb.cb_list); + } else { + list_add_tail(&cb->cb_list, &dev->ctrl_wr_list.mei_cb.cb_list); + } + return rets; +unlock: + mei_free_cb_private(cb); + return rets; +} + +/** + * amthi_write - write iamthif data to amthi client + * + * @dev: the device structure + * @cb: mei call back struct + * + * returns 0 on success, <0 on failure. + */ +int amthi_write(struct mei_device *dev, struct mei_cl_cb *cb) +{ + struct mei_msg_hdr mei_hdr; + int ret; + + if (!dev || !cb) + return -ENODEV; + + dev_dbg(&dev->pdev->dev, "write data to amthi client.\n"); + + dev->iamthif_state = MEI_IAMTHIF_WRITING; + dev->iamthif_current_cb = cb; + dev->iamthif_file_object = cb->file_object; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_msg_buf_size = cb->request_buffer.size; + memcpy(dev->iamthif_msg_buf, cb->request_buffer.data, + cb->request_buffer.size); + + ret = mei_flow_ctrl_creds(dev, &dev->iamthif_cl); + if (ret < 0) + return ret; + + if (ret && dev->mei_host_buffer_is_empty) { + ret = 0; + dev->mei_host_buffer_is_empty = false; + if (cb->request_buffer.size > + (((dev->host_hw_state & H_CBD) >> 24) * sizeof(u32)) + -sizeof(struct mei_msg_hdr)) { + mei_hdr.length = + (((dev->host_hw_state & H_CBD) >> 24) * + sizeof(u32)) - sizeof(struct mei_msg_hdr); + mei_hdr.msg_complete = 0; + } else { + mei_hdr.length = cb->request_buffer.size; + mei_hdr.msg_complete = 1; + } + + mei_hdr.host_addr = dev->iamthif_cl.host_client_id; + mei_hdr.me_addr = dev->iamthif_cl.me_client_id; + mei_hdr.reserved = 0; + dev->iamthif_msg_buf_index += mei_hdr.length; + if (mei_write_message(dev, &mei_hdr, + (unsigned char *)(dev->iamthif_msg_buf), + mei_hdr.length)) + return -ENODEV; + + if (mei_hdr.msg_complete) { + if (mei_flow_ctrl_reduce(dev, &dev->iamthif_cl)) + return -ENODEV; + dev->iamthif_flow_control_pending = true; + dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; + dev_dbg(&dev->pdev->dev, "add amthi cb to write waiting list\n"); + dev->iamthif_current_cb = cb; + dev->iamthif_file_object = cb->file_object; + list_add_tail(&cb->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + } else { + dev_dbg(&dev->pdev->dev, "message does not complete, " + "so add amthi cb to write list.\n"); + list_add_tail(&cb->cb_list, + &dev->write_list.mei_cb.cb_list); + } + } else { + if (!(dev->mei_host_buffer_is_empty)) + dev_dbg(&dev->pdev->dev, "host buffer is not empty"); + + dev_dbg(&dev->pdev->dev, "No flow control credentials, " + "so add iamthif cb to write list.\n"); + list_add_tail(&cb->cb_list, &dev->write_list.mei_cb.cb_list); + } + return 0; +} + +/** + * iamthif_ioctl_send_msg - send cmd data to amthi client + * + * @dev: the device structure + * + * returns 0 on success, <0 on failure. + */ +void mei_run_next_iamthif_cmd(struct mei_device *dev) +{ + struct mei_cl *cl_tmp; + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + int status; + + if (!dev) + return; + + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; + dev->iamthif_file_object = NULL; + + dev_dbg(&dev->pdev->dev, "complete amthi cmd_list cb.\n"); + + list_for_each_entry_safe(pos, next, + &dev->amthi_cmd_list.mei_cb.cb_list, cb_list) { + list_del(&pos->cb_list); + cl_tmp = (struct mei_cl *)pos->file_private; + + if (cl_tmp && cl_tmp == &dev->iamthif_cl) { + status = amthi_write(dev, pos); + if (status) { + dev_dbg(&dev->pdev->dev, + "amthi write failed status = %d\n", + status); + return; + } + break; + } + } +} + +/** + * mei_free_cb_private - free mei_cb_private related memory + * + * @cb: mei callback struct + */ +void mei_free_cb_private(struct mei_cl_cb *cb) +{ + if (cb == NULL) + return; + + kfree(cb->request_buffer.data); + kfree(cb->response_buffer.data); + kfree(cb); +} diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c new file mode 100644 index 000000000000..c70333228337 --- /dev/null +++ b/drivers/misc/mei/main.c @@ -0,0 +1,1230 @@ +/* + * + * 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. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/aio.h> +#include <linux/pci.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/cdev.h> +#include <linux/sched.h> +#include <linux/uuid.h> +#include <linux/compat.h> +#include <linux/jiffies.h> +#include <linux/interrupt.h> +#include <linux/miscdevice.h> + +#include "mei_dev.h" +#include <linux/mei.h> +#include "interface.h" + +static const char mei_driver_name[] = "mei"; + +/* The device pointer */ +/* Currently this driver works as long as there is only a single AMT device. */ +struct pci_dev *mei_device; + +/* mei_pci_tbl - PCI Device ID Table */ +static DEFINE_PCI_DEVICE_TABLE(mei_pci_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, + + /* required last entry */ + {0, } +}; + +MODULE_DEVICE_TABLE(pci, mei_pci_tbl); + +static DEFINE_MUTEX(mei_mutex); + + +/** + * mei_clear_list - removes all callbacks associated with file + * from mei_cb_list + * + * @dev: device structure. + * @file: file structure + * @mei_cb_list: callbacks list + * + * mei_clear_list is called to clear resources associated with file + * when application calls close function or Ctrl-C was pressed + * + * returns true if callback removed from the list, false otherwise + */ +static bool mei_clear_list(struct mei_device *dev, + struct file *file, struct list_head *mei_cb_list) +{ + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb_next = NULL; + struct file *file_temp; + bool removed = false; + + /* list all list member */ + list_for_each_entry_safe(cb_pos, cb_next, mei_cb_list, cb_list) { + file_temp = (struct file *)cb_pos->file_object; + /* check if list member associated with a file */ + if (file_temp == file) { + /* remove member from the list */ + list_del(&cb_pos->cb_list); + /* check if cb equal to current iamthif cb */ + if (dev->iamthif_current_cb == cb_pos) { + dev->iamthif_current_cb = NULL; + /* send flow control to iamthif client */ + mei_send_flow_control(dev, &dev->iamthif_cl); + } + /* free all allocated buffers */ + mei_free_cb_private(cb_pos); + cb_pos = NULL; + removed = true; + } + } + return removed; +} + +/** + * mei_clear_lists - removes all callbacks associated with file + * + * @dev: device structure + * @file: file structure + * + * mei_clear_lists is called to clear resources associated with file + * when application calls close function or Ctrl-C was pressed + * + * returns true if callback removed from the list, false otherwise + */ +static bool mei_clear_lists(struct mei_device *dev, struct file *file) +{ + bool removed = false; + + /* remove callbacks associated with a file */ + mei_clear_list(dev, file, &dev->amthi_cmd_list.mei_cb.cb_list); + if (mei_clear_list(dev, file, + &dev->amthi_read_complete_list.mei_cb.cb_list)) + removed = true; + + mei_clear_list(dev, file, &dev->ctrl_rd_list.mei_cb.cb_list); + + if (mei_clear_list(dev, file, &dev->ctrl_wr_list.mei_cb.cb_list)) + removed = true; + + if (mei_clear_list(dev, file, &dev->write_waiting_list.mei_cb.cb_list)) + removed = true; + + if (mei_clear_list(dev, file, &dev->write_list.mei_cb.cb_list)) + removed = true; + + /* 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) { + /* remove cb */ + mei_free_cb_private(dev->iamthif_current_cb); + dev->iamthif_current_cb = NULL; + removed = true; + } + } + return removed; +} +/** + * find_read_list_entry - find read list entry + * + * @dev: device structure + * @file: pointer to file structure + * + * returns cb on success, NULL on error + */ +static struct mei_cl_cb *find_read_list_entry( + struct mei_device *dev, + struct mei_cl *cl) +{ + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + + dev_dbg(&dev->pdev->dev, "remove read_list CB\n"); + list_for_each_entry_safe(pos, next, + &dev->read_list.mei_cb.cb_list, cb_list) { + struct mei_cl *cl_temp; + cl_temp = (struct mei_cl *)pos->file_private; + + if (mei_cl_cmp_id(cl, cl_temp)) + return pos; + } + return NULL; +} + +/** + * mei_open - the open function + * + * @inode: pointer to inode structure + * @file: pointer to file structure + * + * returns 0 on success, <0 on error + */ +static int mei_open(struct inode *inode, struct file *file) +{ + struct mei_cl *cl; + struct mei_device *dev; + unsigned long cl_id; + int err; + + err = -ENODEV; + if (!mei_device) + goto out; + + dev = pci_get_drvdata(mei_device); + if (!dev) + goto out; + + mutex_lock(&dev->device_lock); + err = -ENOMEM; + cl = mei_cl_allocate(dev); + if (!cl) + goto out_unlock; + + err = -ENODEV; + if (dev->mei_state != MEI_ENABLED) { + dev_dbg(&dev->pdev->dev, "mei_state != MEI_ENABLED mei_state= %d\n", + dev->mei_state); + goto out_unlock; + } + err = -EMFILE; + if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) + goto out_unlock; + + cl_id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); + if (cl_id >= MEI_CLIENTS_MAX) + goto out_unlock; + + cl->host_client_id = cl_id; + + dev_dbg(&dev->pdev->dev, "client_id = %d\n", cl->host_client_id); + + dev->open_handle_count++; + + list_add_tail(&cl->link, &dev->file_list); + + set_bit(cl->host_client_id, dev->host_clients_map); + cl->state = MEI_FILE_INITIALIZING; + cl->sm_state = 0; + + file->private_data = cl; + mutex_unlock(&dev->device_lock); + + return nonseekable_open(inode, file); + +out_unlock: + mutex_unlock(&dev->device_lock); + kfree(cl); +out: + return err; +} + +/** + * mei_release - the release function + * + * @inode: pointer to inode structure + * @file: pointer to file structure + * + * returns 0 on success, <0 on error + */ +static int mei_release(struct inode *inode, struct file *file) +{ + struct mei_cl *cl = file->private_data; + struct mei_cl_cb *cb; + struct mei_device *dev; + int rets = 0; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + if (cl != &dev->iamthif_cl) { + if (cl->state == MEI_FILE_CONNECTED) { + cl->state = MEI_FILE_DISCONNECTING; + dev_dbg(&dev->pdev->dev, + "disconnecting client host client = %d, " + "ME client = %d\n", + cl->host_client_id, + cl->me_client_id); + rets = mei_disconnect_host_client(dev, cl); + } + mei_cl_flush_queues(cl); + dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n", + cl->host_client_id, + cl->me_client_id); + + if (dev->open_handle_count > 0) { + clear_bit(cl->host_client_id, dev->host_clients_map); + dev->open_handle_count--; + } + mei_remove_client_from_file_list(dev, cl->host_client_id); + + /* free read cb */ + cb = NULL; + if (cl->read_cb) { + cb = find_read_list_entry(dev, cl); + /* Remove entry from read list */ + if (cb) + list_del(&cb->cb_list); + + cb = cl->read_cb; + cl->read_cb = NULL; + } + + file->private_data = NULL; + + if (cb) { + mei_free_cb_private(cb); + cb = NULL; + } + + kfree(cl); + } else { + if (dev->open_handle_count > 0) + dev->open_handle_count--; + + if (dev->iamthif_file_object == file && + dev->iamthif_state != MEI_IAMTHIF_IDLE) { + + dev_dbg(&dev->pdev->dev, "amthi canceled iamthif state %d\n", + dev->iamthif_state); + dev->iamthif_canceled = true; + if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE) { + dev_dbg(&dev->pdev->dev, "run next amthi iamthif cb\n"); + mei_run_next_iamthif_cmd(dev); + } + } + + if (mei_clear_lists(dev, file)) + dev->iamthif_state = MEI_IAMTHIF_IDLE; + + } + mutex_unlock(&dev->device_lock); + return rets; +} + + +/** + * mei_read - the read function. + * + * @file: pointer to file structure + * @ubuf: pointer to user buffer + * @length: buffer length + * @offset: data offset in buffer + * + * returns >=0 data length on success , <0 on error + */ +static ssize_t mei_read(struct file *file, char __user *ubuf, + size_t length, loff_t *offset) +{ + struct mei_cl *cl = file->private_data; + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb = NULL; + struct mei_device *dev; + int i; + int rets; + int err; + + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto out; + } + + if ((cl->sm_state & MEI_WD_STATE_INDEPENDENCE_MSG_SENT) == 0) { + /* Do not allow to read watchdog client */ + i = mei_find_me_client_index(dev, mei_wd_guid); + if (i >= 0) { + struct mei_me_client *me_client = &dev->me_clients[i]; + + if (cl->me_client_id == me_client->client_id) { + rets = -EBADF; + goto out; + } + } + } else { + cl->sm_state &= ~MEI_WD_STATE_INDEPENDENCE_MSG_SENT; + } + + if (cl == &dev->iamthif_cl) { + rets = amthi_read(dev, file, ubuf, length, offset); + goto out; + } + + if (cl->read_cb && cl->read_cb->information > *offset) { + cb = cl->read_cb; + goto copy_buffer; + } else if (cl->read_cb && cl->read_cb->information > 0 && + cl->read_cb->information <= *offset) { + cb = cl->read_cb; + rets = 0; + goto free; + } else if ((!cl->read_cb || !cl->read_cb->information) && + *offset > 0) { + /*Offset needs to be cleaned for contiguous reads*/ + *offset = 0; + rets = 0; + goto out; + } + + err = mei_start_read(dev, cl); + if (err && err != -EBUSY) { + dev_dbg(&dev->pdev->dev, + "mei start read failure with status = %d\n", err); + rets = err; + goto out; + } + + if (MEI_READ_COMPLETE != cl->reading_state && + !waitqueue_active(&cl->rx_wait)) { + if (file->f_flags & O_NONBLOCK) { + rets = -EAGAIN; + goto out; + } + + mutex_unlock(&dev->device_lock); + + if (wait_event_interruptible(cl->rx_wait, + (MEI_READ_COMPLETE == cl->reading_state || + MEI_FILE_INITIALIZING == cl->state || + MEI_FILE_DISCONNECTED == cl->state || + MEI_FILE_DISCONNECTING == cl->state))) { + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } + + mutex_lock(&dev->device_lock); + if (MEI_FILE_INITIALIZING == cl->state || + MEI_FILE_DISCONNECTED == cl->state || + MEI_FILE_DISCONNECTING == cl->state) { + rets = -EBUSY; + goto out; + } + } + + cb = cl->read_cb; + + if (!cb) { + rets = -ENODEV; + goto out; + } + if (cl->reading_state != MEI_READ_COMPLETE) { + rets = 0; + goto out; + } + /* now copy the data to user space */ +copy_buffer: + dev_dbg(&dev->pdev->dev, "cb->response_buffer size - %d\n", + cb->response_buffer.size); + dev_dbg(&dev->pdev->dev, "cb->information - %lu\n", + cb->information); + if (length == 0 || ubuf == NULL || *offset > cb->information) { + rets = -EMSGSIZE; + goto free; + } + + /* length is being truncated to PAGE_SIZE, however, */ + /* information size may be longer */ + length = min_t(size_t, length, (cb->information - *offset)); + + if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { + rets = -EFAULT; + goto free; + } + + rets = length; + *offset += length; + if ((unsigned long)*offset < cb->information) + goto out; + +free: + cb_pos = find_read_list_entry(dev, cl); + /* Remove entry from read list */ + if (cb_pos) + list_del(&cb_pos->cb_list); + mei_free_cb_private(cb); + cl->reading_state = MEI_IDLE; + cl->read_cb = NULL; + cl->read_pending = 0; +out: + dev_dbg(&dev->pdev->dev, "end mei read rets= %d\n", rets); + mutex_unlock(&dev->device_lock); + return rets; +} + +/** + * mei_write - the write function. + * + * @file: pointer to file structure + * @ubuf: pointer to user buffer + * @length: buffer length + * @offset: data offset in buffer + * + * returns >=0 data length on success , <0 on error + */ +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_msg_hdr mei_hdr; + struct mei_device *dev; + unsigned long timeout = 0; + int rets; + int i; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (dev->mei_state != MEI_ENABLED) { + mutex_unlock(&dev->device_lock); + return -ENODEV; + } + + if (cl == &dev->iamthif_cl) { + write_cb = find_amthi_read_list_entry(dev, file); + + if (write_cb) { + timeout = write_cb->read_time + + msecs_to_jiffies(IAMTHIF_READ_TIMER); + + if (time_after(jiffies, timeout) || + cl->reading_state == MEI_READ_COMPLETE) { + *offset = 0; + list_del(&write_cb->cb_list); + mei_free_cb_private(write_cb); + write_cb = NULL; + } + } + } + + /* free entry used in read */ + if (cl->reading_state == MEI_READ_COMPLETE) { + *offset = 0; + write_cb = find_read_list_entry(dev, cl); + if (write_cb) { + list_del(&write_cb->cb_list); + mei_free_cb_private(write_cb); + write_cb = NULL; + cl->reading_state = MEI_IDLE; + cl->read_cb = NULL; + cl->read_pending = 0; + } + } else if (cl->reading_state == MEI_IDLE && !cl->read_pending) + *offset = 0; + + + write_cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!write_cb) { + mutex_unlock(&dev->device_lock); + return -ENOMEM; + } + + write_cb->file_object = file; + write_cb->file_private = cl; + write_cb->request_buffer.data = kmalloc(length, GFP_KERNEL); + rets = -ENOMEM; + if (!write_cb->request_buffer.data) + goto unlock_dev; + + dev_dbg(&dev->pdev->dev, "length =%d\n", (int) length); + + rets = -EFAULT; + if (copy_from_user(write_cb->request_buffer.data, ubuf, length)) + goto unlock_dev; + + cl->sm_state = 0; + if (length == 4 && + ((memcmp(mei_wd_state_independence_msg[0], + write_cb->request_buffer.data, 4) == 0) || + (memcmp(mei_wd_state_independence_msg[1], + write_cb->request_buffer.data, 4) == 0) || + (memcmp(mei_wd_state_independence_msg[2], + write_cb->request_buffer.data, 4) == 0))) + cl->sm_state |= MEI_WD_STATE_INDEPENDENCE_MSG_SENT; + + INIT_LIST_HEAD(&write_cb->cb_list); + if (cl == &dev->iamthif_cl) { + write_cb->response_buffer.data = + kmalloc(dev->iamthif_mtu, GFP_KERNEL); + if (!write_cb->response_buffer.data) { + rets = -ENOMEM; + goto unlock_dev; + } + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto unlock_dev; + } + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == + dev->iamthif_cl.me_client_id) + break; + } + + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) { + rets = -ENODEV; + goto unlock_dev; + } + if (i == dev->me_clients_num || + (dev->me_clients[i].client_id != + dev->iamthif_cl.me_client_id)) { + rets = -ENODEV; + goto unlock_dev; + } else if (length > dev->me_clients[i].props.max_msg_length || + length <= 0) { + rets = -EMSGSIZE; + goto unlock_dev; + } + + write_cb->response_buffer.size = dev->iamthif_mtu; + write_cb->major_file_operations = MEI_IOCTL; + write_cb->information = 0; + write_cb->request_buffer.size = length; + if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + goto unlock_dev; + } + + if (!list_empty(&dev->amthi_cmd_list.mei_cb.cb_list) || + dev->iamthif_state != MEI_IAMTHIF_IDLE) { + dev_dbg(&dev->pdev->dev, "amthi_state = %d\n", + (int) dev->iamthif_state); + dev_dbg(&dev->pdev->dev, "add amthi cb to amthi cmd waiting list\n"); + list_add_tail(&write_cb->cb_list, + &dev->amthi_cmd_list.mei_cb.cb_list); + rets = length; + } else { + dev_dbg(&dev->pdev->dev, "call amthi write\n"); + rets = amthi_write(dev, write_cb); + + if (rets) { + dev_dbg(&dev->pdev->dev, "amthi write failed with status = %d\n", + rets); + goto unlock_dev; + } + rets = length; + } + mutex_unlock(&dev->device_lock); + return rets; + } + + write_cb->major_file_operations = MEI_WRITE; + /* make sure information is zero before we start */ + + write_cb->information = 0; + write_cb->request_buffer.size = length; + + dev_dbg(&dev->pdev->dev, "host client = %d, ME client = %d\n", + cl->host_client_id, cl->me_client_id); + if (cl->state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + dev_dbg(&dev->pdev->dev, "host client = %d, is not connected to ME client = %d", + cl->host_client_id, + cl->me_client_id); + goto unlock_dev; + } + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == + cl->me_client_id) + break; + } + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) { + rets = -ENODEV; + goto unlock_dev; + } + if (i == dev->me_clients_num) { + rets = -ENODEV; + goto unlock_dev; + } + if (length > dev->me_clients[i].props.max_msg_length || length <= 0) { + rets = -EINVAL; + goto unlock_dev; + } + write_cb->file_private = cl; + + rets = mei_flow_ctrl_creds(dev, cl); + if (rets < 0) + goto unlock_dev; + + if (rets && dev->mei_host_buffer_is_empty) { + rets = 0; + dev->mei_host_buffer_is_empty = false; + if (length > ((((dev->host_hw_state & H_CBD) >> 24) * + sizeof(u32)) - sizeof(struct mei_msg_hdr))) { + + mei_hdr.length = + (((dev->host_hw_state & H_CBD) >> 24) * + sizeof(u32)) - + sizeof(struct mei_msg_hdr); + mei_hdr.msg_complete = 0; + } else { + mei_hdr.length = length; + mei_hdr.msg_complete = 1; + } + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; + dev_dbg(&dev->pdev->dev, "call mei_write_message header=%08x.\n", + *((u32 *) &mei_hdr)); + if (mei_write_message(dev, &mei_hdr, + (unsigned char *) (write_cb->request_buffer.data), + mei_hdr.length)) { + rets = -ENODEV; + goto unlock_dev; + } + cl->writing_state = MEI_WRITING; + write_cb->information = mei_hdr.length; + if (mei_hdr.msg_complete) { + if (mei_flow_ctrl_reduce(dev, cl)) { + rets = -ENODEV; + goto unlock_dev; + } + list_add_tail(&write_cb->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + } else { + list_add_tail(&write_cb->cb_list, + &dev->write_list.mei_cb.cb_list); + } + + } else { + + write_cb->information = 0; + cl->writing_state = MEI_WRITING; + list_add_tail(&write_cb->cb_list, + &dev->write_list.mei_cb.cb_list); + } + mutex_unlock(&dev->device_lock); + return length; + +unlock_dev: + mutex_unlock(&dev->device_lock); + mei_free_cb_private(write_cb); + return rets; +} + + +/** + * mei_ioctl - the IOCTL function + * + * @file: pointer to file structure + * @cmd: ioctl command + * @data: pointer to mei message structure + * + * returns 0 on success , <0 on error + */ +static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) +{ + struct mei_device *dev; + struct mei_cl *cl = file->private_data; + struct mei_connect_client_data *connect_data = NULL; + int rets; + + if (cmd != IOCTL_MEI_CONNECT_CLIENT) + return -EINVAL; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + dev_dbg(&dev->pdev->dev, "IOCTL cmd = 0x%x", cmd); + + mutex_lock(&dev->device_lock); + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto out; + } + + dev_dbg(&dev->pdev->dev, ": IOCTL_MEI_CONNECT_CLIENT.\n"); + + connect_data = kzalloc(sizeof(struct mei_connect_client_data), + GFP_KERNEL); + if (!connect_data) { + rets = -ENOMEM; + goto out; + } + dev_dbg(&dev->pdev->dev, "copy connect data from user\n"); + if (copy_from_user(connect_data, (char __user *)data, + sizeof(struct mei_connect_client_data))) { + dev_dbg(&dev->pdev->dev, "failed to copy data from userland\n"); + rets = -EFAULT; + goto out; + } + rets = mei_ioctl_connect_client(file, connect_data); + + /* if all is ok, copying the data back to user. */ + if (rets) + goto out; + + dev_dbg(&dev->pdev->dev, "copy connect data to user\n"); + if (copy_to_user((char __user *)data, connect_data, + sizeof(struct mei_connect_client_data))) { + dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n"); + rets = -EFAULT; + goto out; + } + +out: + kfree(connect_data); + mutex_unlock(&dev->device_lock); + return rets; +} + +/** + * mei_compat_ioctl - the compat IOCTL function + * + * @file: pointer to file structure + * @cmd: ioctl command + * @data: pointer to mei message structure + * + * returns 0 on success , <0 on error + */ +#ifdef CONFIG_COMPAT +static long mei_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long data) +{ + return mei_ioctl(file, cmd, (unsigned long)compat_ptr(data)); +} +#endif + + +/** + * mei_poll - the poll function + * + * @file: pointer to file structure + * @wait: pointer to poll_table structure + * + * returns poll mask + */ +static unsigned int mei_poll(struct file *file, poll_table *wait) +{ + struct mei_cl *cl = file->private_data; + struct mei_device *dev; + unsigned int mask = 0; + + if (WARN_ON(!cl || !cl->dev)) + return mask; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (dev->mei_state != MEI_ENABLED) + goto out; + + + if (cl == &dev->iamthif_cl) { + mutex_unlock(&dev->device_lock); + poll_wait(file, &dev->iamthif_cl.wait, wait); + mutex_lock(&dev->device_lock); + if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && + dev->iamthif_file_object == file) { + mask |= (POLLIN | POLLRDNORM); + dev_dbg(&dev->pdev->dev, "run next amthi cb\n"); + mei_run_next_iamthif_cmd(dev); + } + goto out; + } + + mutex_unlock(&dev->device_lock); + poll_wait(file, &cl->tx_wait, wait); + mutex_lock(&dev->device_lock); + if (MEI_WRITE_COMPLETE == cl->writing_state) + mask |= (POLLIN | POLLRDNORM); + +out: + mutex_unlock(&dev->device_lock); + return mask; +} + +/* + * file operations structure will be used for mei char device. + */ +static const struct file_operations mei_fops = { + .owner = THIS_MODULE, + .read = mei_read, + .unlocked_ioctl = mei_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = mei_compat_ioctl, +#endif + .open = mei_open, + .release = mei_release, + .write = mei_write, + .poll = mei_poll, + .llseek = no_llseek +}; + + +/* + * Misc Device Struct + */ +static struct miscdevice mei_misc_device = { + .name = "mei", + .fops = &mei_fops, + .minor = MISC_DYNAMIC_MINOR, +}; + +/** + * mei_probe - Device Initialization Routine + * + * @pdev: PCI device structure + * @ent: entry in kcs_pci_tbl + * + * returns 0 on success, <0 on failure. + */ +static int __devinit mei_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mei_device *dev; + int err; + + mutex_lock(&mei_mutex); + if (mei_device) { + err = -EEXIST; + goto end; + } + /* enable pci dev */ + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "failed to enable pci device.\n"); + goto end; + } + /* set PCI host mastering */ + pci_set_master(pdev); + /* pci request regions for mei driver */ + err = pci_request_regions(pdev, mei_driver_name); + if (err) { + dev_err(&pdev->dev, "failed to get pci regions.\n"); + goto disable_device; + } + /* allocates and initializes the mei dev structure */ + dev = mei_device_init(pdev); + if (!dev) { + err = -ENOMEM; + goto release_regions; + } + /* mapping IO device memory */ + dev->mem_addr = pci_iomap(pdev, 0, 0); + if (!dev->mem_addr) { + dev_err(&pdev->dev, "mapping I/O device memory failure.\n"); + err = -ENOMEM; + goto free_device; + } + pci_enable_msi(pdev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_interrupt_thread_handler, + 0, mei_driver_name, dev); + else + err = request_threaded_irq(pdev->irq, + mei_interrupt_quick_handler, + mei_interrupt_thread_handler, + IRQF_SHARED, mei_driver_name, dev); + + if (err) { + dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n", + pdev->irq); + goto unmap_memory; + } + INIT_DELAYED_WORK(&dev->timer_work, mei_timer); + if (mei_hw_init(dev)) { + dev_err(&pdev->dev, "init hw failure.\n"); + err = -ENODEV; + goto release_irq; + } + + err = misc_register(&mei_misc_device); + if (err) + goto release_irq; + + mei_device = pdev; + pci_set_drvdata(pdev, dev); + + + schedule_delayed_work(&dev->timer_work, HZ); + + mutex_unlock(&mei_mutex); + + pr_debug("initialization successful.\n"); + + return 0; + +release_irq: + /* disable interrupts */ + dev->host_hw_state = mei_hcsr_read(dev); + mei_disable_interrupts(dev); + flush_scheduled_work(); + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); +unmap_memory: + pci_iounmap(pdev, dev->mem_addr); +free_device: + kfree(dev); +release_regions: + pci_release_regions(pdev); +disable_device: + pci_disable_device(pdev); +end: + mutex_unlock(&mei_mutex); + dev_err(&pdev->dev, "initialization failed.\n"); + return err; +} + +/** + * mei_remove - Device Removal Routine + * + * @pdev: PCI device structure + * + * mei_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. + */ +static void __devexit mei_remove(struct pci_dev *pdev) +{ + struct mei_device *dev; + + if (mei_device != pdev) + return; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + mutex_lock(&dev->device_lock); + + mei_wd_stop(dev, false); + + mei_device = NULL; + + if (dev->iamthif_cl.state == MEI_FILE_CONNECTED) { + dev->iamthif_cl.state = MEI_FILE_DISCONNECTING; + mei_disconnect_host_client(dev, &dev->iamthif_cl); + } + if (dev->wd_cl.state == MEI_FILE_CONNECTED) { + dev->wd_cl.state = MEI_FILE_DISCONNECTING; + mei_disconnect_host_client(dev, &dev->wd_cl); + } + + /* Unregistering watchdog device */ + mei_watchdog_unregister(dev); + + /* remove entry if already in list */ + dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); + mei_remove_client_from_file_list(dev, dev->wd_cl.host_client_id); + mei_remove_client_from_file_list(dev, dev->iamthif_cl.host_client_id); + + dev->iamthif_current_cb = NULL; + dev->me_clients_num = 0; + + mutex_unlock(&dev->device_lock); + + flush_scheduled_work(); + + /* disable interrupts */ + mei_disable_interrupts(dev); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + pci_set_drvdata(pdev, NULL); + + if (dev->mem_addr) + pci_iounmap(pdev, dev->mem_addr); + + kfree(dev); + + pci_release_regions(pdev); + pci_disable_device(pdev); +} +#ifdef CONFIG_PM +static int mei_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev = pci_get_drvdata(pdev); + int err; + + if (!dev) + return -ENODEV; + mutex_lock(&dev->device_lock); + /* Stop watchdog if exists */ + err = mei_wd_stop(dev, true); + /* Set new mei state */ + if (dev->mei_state == MEI_ENABLED || + dev->mei_state == MEI_RECOVERING_FROM_RESET) { + dev->mei_state = MEI_POWER_DOWN; + mei_reset(dev, 0); + } + mutex_unlock(&dev->device_lock); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + + return err; +} + +static int mei_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int err; + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + pci_enable_msi(pdev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_interrupt_thread_handler, + 0, mei_driver_name, dev); + else + err = request_threaded_irq(pdev->irq, + mei_interrupt_quick_handler, + mei_interrupt_thread_handler, + IRQF_SHARED, mei_driver_name, dev); + + if (err) { + dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n", + pdev->irq); + return err; + } + + mutex_lock(&dev->device_lock); + dev->mei_state = MEI_POWER_UP; + mei_reset(dev, 1); + mutex_unlock(&dev->device_lock); + + /* Start timer if stopped in suspend */ + schedule_delayed_work(&dev->timer_work, HZ); + + return err; +} +static SIMPLE_DEV_PM_OPS(mei_pm_ops, mei_pci_suspend, mei_pci_resume); +#define MEI_PM_OPS (&mei_pm_ops) +#else +#define MEI_PM_OPS NULL +#endif /* CONFIG_PM */ +/* + * PCI driver structure + */ +static struct pci_driver mei_driver = { + .name = mei_driver_name, + .id_table = mei_pci_tbl, + .probe = mei_probe, + .remove = __devexit_p(mei_remove), + .shutdown = __devexit_p(mei_remove), + .driver.pm = MEI_PM_OPS, +}; + +/** + * mei_init_module - Driver Registration Routine + * + * mei_init_module is the first routine called when the driver is + * loaded. All it does is to register with the PCI subsystem. + * + * returns 0 on success, <0 on failure. + */ +static int __init mei_init_module(void) +{ + int ret; + + pr_debug("loading.\n"); + /* init pci module */ + ret = pci_register_driver(&mei_driver); + if (ret < 0) + pr_err("error registering driver.\n"); + + return ret; +} + +module_init(mei_init_module); + +/** + * mei_exit_module - Driver Exit Cleanup Routine + * + * mei_exit_module is called just before the driver is removed + * from memory. + */ +static void __exit mei_exit_module(void) +{ + misc_deregister(&mei_misc_device); + pci_unregister_driver(&mei_driver); + + pr_debug("unloaded successfully.\n"); +} + +module_exit(mei_exit_module); + + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) Management Engine Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h new file mode 100644 index 000000000000..63d7ee97c5fb --- /dev/null +++ b/drivers/misc/mei/mei_dev.h @@ -0,0 +1,428 @@ +/* + * + * 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. + * + */ + +#ifndef _MEI_DEV_H_ +#define _MEI_DEV_H_ + +#include <linux/types.h> +#include <linux/watchdog.h> +#include <linux/mei.h> +#include "hw.h" + +/* + * watch dog definition + */ +#define MEI_WATCHDOG_DATA_SIZE 16 +#define MEI_START_WD_DATA_SIZE 20 +#define MEI_WD_PARAMS_SIZE 4 +#define MEI_WD_STATE_INDEPENDENCE_MSG_SENT (1 << 0) + +#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32)) + +/* + * MEI PCI Device object + */ +extern struct pci_dev *mei_device; + + +/* + * AMTHI Client UUID + */ +extern const uuid_le mei_amthi_guid; + +/* + * Watchdog Client UUID + */ +extern const uuid_le mei_wd_guid; + +/* + * Watchdog independence state message + */ +extern const u8 mei_wd_state_independence_msg[3][4]; + +/* + * Number of File descriptors/handles + * that can be opened to the driver. + * + * Limit to 253: 255 Total Clients + * minus internal client for AMTHI + * minus internal client for Watchdog + */ +#define MEI_MAX_OPEN_HANDLE_COUNT 253 + +/* + * Number of Maximum MEI Clients + */ +#define MEI_CLIENTS_MAX 255 + +/* File state */ +enum file_state { + MEI_FILE_INITIALIZING = 0, + MEI_FILE_CONNECTING, + MEI_FILE_CONNECTED, + MEI_FILE_DISCONNECTING, + MEI_FILE_DISCONNECTED +}; + +/* MEI device states */ +enum mei_states { + MEI_INITIALIZING = 0, + MEI_INIT_CLIENTS, + MEI_ENABLED, + MEI_RESETING, + MEI_DISABLED, + MEI_RECOVERING_FROM_RESET, + MEI_POWER_DOWN, + MEI_POWER_UP +}; + +/* init clients states*/ +enum mei_init_clients_states { + MEI_START_MESSAGE = 0, + MEI_ENUM_CLIENTS_MESSAGE, + MEI_CLIENT_PROPERTIES_MESSAGE +}; + +enum iamthif_states { + MEI_IAMTHIF_IDLE, + MEI_IAMTHIF_WRITING, + MEI_IAMTHIF_FLOW_CONTROL, + MEI_IAMTHIF_READING, + MEI_IAMTHIF_READ_COMPLETE +}; + +enum mei_file_transaction_states { + MEI_IDLE, + MEI_WRITING, + MEI_WRITE_COMPLETE, + MEI_FLOW_CONTROL, + MEI_READING, + MEI_READ_COMPLETE +}; + +/* MEI CB */ +enum mei_cb_major_types { + MEI_READ = 0, + MEI_WRITE, + MEI_IOCTL, + MEI_OPEN, + MEI_CLOSE +}; + +/* + * Intel MEI message data struct + */ +struct mei_message_data { + u32 size; + unsigned char *data; +} __packed; + + +struct mei_cl_cb { + struct list_head cb_list; + enum mei_cb_major_types major_file_operations; + void *file_private; + struct mei_message_data request_buffer; + struct mei_message_data response_buffer; + unsigned long information; + unsigned long read_time; + struct file *file_object; +}; + +/* MEI client instance carried as file->pirvate_data*/ +struct mei_cl { + struct list_head link; + struct mei_device *dev; + enum file_state state; + wait_queue_head_t tx_wait; + wait_queue_head_t rx_wait; + wait_queue_head_t wait; + int read_pending; + int status; + /* ID of client connected */ + u8 host_client_id; + u8 me_client_id; + u8 mei_flow_ctrl_creds; + u8 timer_count; + enum mei_file_transaction_states reading_state; + enum mei_file_transaction_states writing_state; + int sm_state; + struct mei_cl_cb *read_cb; +}; + +struct mei_io_list { + struct mei_cl_cb mei_cb; +}; + +/* MEI private device struct */ +struct mei_device { + struct pci_dev *pdev; /* pointer to pci device struct */ + /* + * lists of queues + */ + /* array of pointers to aio lists */ + struct mei_io_list read_list; /* driver read queue */ + struct mei_io_list write_list; /* driver write queue */ + struct mei_io_list write_waiting_list; /* write waiting queue */ + struct mei_io_list ctrl_wr_list; /* managed write IOCTL list */ + struct mei_io_list ctrl_rd_list; /* managed read IOCTL list */ + struct mei_io_list amthi_cmd_list; /* amthi list for cmd waiting */ + + /* driver managed amthi list for reading completed amthi cmd data */ + struct mei_io_list amthi_read_complete_list; + /* + * list of files + */ + struct list_head file_list; + long open_handle_count; + /* + * memory of device + */ + unsigned int mem_base; + unsigned int mem_length; + void __iomem *mem_addr; + /* + * lock for the device + */ + struct mutex device_lock; /* device lock */ + struct delayed_work timer_work; /* MEI timer delayed work (timeouts) */ + bool recvd_msg; + /* + * hw states of host and fw(ME) + */ + u32 host_hw_state; + u32 me_hw_state; + /* + * waiting queue for receive message from FW + */ + wait_queue_head_t wait_recvd_msg; + wait_queue_head_t wait_stop_wd; + + /* + * mei device states + */ + enum mei_states mei_state; + enum mei_init_clients_states init_clients_state; + u16 init_clients_timer; + bool stop; + bool need_reset; + + u32 extra_write_index; + unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ + u32 wr_msg_buf[128]; /* used for control messages */ + u32 ext_msg_buf[8]; /* for control responses */ + u32 rd_msg_hdr; + + struct hbm_version version; + + struct mei_me_client *me_clients; /* Note: memory has to be allocated */ + DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX); + DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX); + u8 me_clients_num; + u8 me_client_presentation_num; + u8 me_client_index; + bool mei_host_buffer_is_empty; + + struct mei_cl wd_cl; + bool wd_pending; + bool wd_stopped; + bool wd_bypass; /* if false, don't refresh watchdog ME client */ + u16 wd_timeout; /* seconds ((wd_data[1] << 8) + wd_data[0]) */ + u16 wd_due_counter; + unsigned char wd_data[MEI_START_WD_DATA_SIZE]; + + + + struct file *iamthif_file_object; + struct mei_cl iamthif_cl; + struct mei_cl_cb *iamthif_current_cb; + int iamthif_mtu; + unsigned long iamthif_timer; + u32 iamthif_stall_timer; + unsigned char *iamthif_msg_buf; /* Note: memory has to be allocated */ + u32 iamthif_msg_buf_size; + u32 iamthif_msg_buf_index; + enum iamthif_states iamthif_state; + bool iamthif_flow_control_pending; + bool iamthif_ioctl; + bool iamthif_canceled; + + bool wd_interface_reg; +}; + + +/* + * mei init function prototypes + */ +struct mei_device *mei_device_init(struct pci_dev *pdev); +void mei_reset(struct mei_device *dev, int interrupts); +int mei_hw_init(struct mei_device *dev); +int mei_task_initialize_clients(void *data); +int mei_initialize_clients(struct mei_device *dev); +int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl); +void mei_remove_client_from_file_list(struct mei_device *dev, u8 host_client_id); +void mei_host_init_iamthif(struct mei_device *dev); +void mei_allocate_me_clients_storage(struct mei_device *dev); + + +u8 mei_find_me_client_update_filext(struct mei_device *dev, + struct mei_cl *priv, + const uuid_le *cguid, u8 client_id); + +/* + * MEI IO List Functions + */ +void mei_io_list_init(struct mei_io_list *list); +void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl); + +/* + * MEI ME Client Functions + */ + +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_flush_queues(struct mei_cl *cl); +/** + * mei_cl_cmp_id - tells if file private data have same id + * + * @fe1: private data of 1. file object + * @fe2: private data of 2. file object + * + * returns true - if ids are the same and not NULL + */ +static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, + const struct mei_cl *cl2) +{ + return cl1 && cl2 && + (cl1->host_client_id == cl2->host_client_id) && + (cl1->me_client_id == cl2->me_client_id); +} + + + +/* + * MEI Host Client Functions + */ +void mei_host_start_message(struct mei_device *dev); +void mei_host_enum_clients_message(struct mei_device *dev); +int mei_host_client_properties(struct mei_device *dev); + +/* + * MEI interrupt functions prototype + */ +irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id); +irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id); +void mei_timer(struct work_struct *work); + +/* + * MEI input output function prototype + */ +int mei_ioctl_connect_client(struct file *file, + struct mei_connect_client_data *data); + +int mei_start_read(struct mei_device *dev, struct mei_cl *cl); + +int amthi_write(struct mei_device *dev, struct mei_cl_cb *priv_cb); + +int amthi_read(struct mei_device *dev, struct file *file, + char __user *ubuf, size_t length, loff_t *offset); + +struct mei_cl_cb *find_amthi_read_list_entry(struct mei_device *dev, + struct file *file); + +void mei_run_next_iamthif_cmd(struct mei_device *dev); + +void mei_free_cb_private(struct mei_cl_cb *priv_cb); + +int mei_find_me_client_index(const struct mei_device *dev, uuid_le cuuid); + +/* + * Register Access Function + */ + +/** + * mei_reg_read - Reads 32bit data from the mei device + * + * @dev: the device structure + * @offset: offset from which to read the data + * + * returns register value (u32) + */ +static inline u32 mei_reg_read(struct mei_device *dev, unsigned long offset) +{ + return ioread32(dev->mem_addr + offset); +} + +/** + * mei_reg_write - Writes 32bit data to the mei device + * + * @dev: the device structure + * @offset: offset from which to write the data + * @value: register value to write (u32) + */ +static inline void mei_reg_write(struct mei_device *dev, + unsigned long offset, u32 value) +{ + iowrite32(value, dev->mem_addr + offset); +} + +/** + * mei_hcsr_read - Reads 32bit data from the host CSR + * + * @dev: the device structure + * + * returns the byte read. + */ +static inline u32 mei_hcsr_read(struct mei_device *dev) +{ + return mei_reg_read(dev, H_CSR); +} + +/** + * mei_mecsr_read - Reads 32bit data from the ME CSR + * + * @dev: the device structure + * + * returns ME_CSR_HA register value (u32) + */ +static inline u32 mei_mecsr_read(struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CSR_HA); +} + +/** + * get_me_cb_rw - Reads 32bit data from the mei ME_CB_RW register + * + * @dev: the device structure + * + * returns ME_CB_RW register value (u32) + */ +static inline u32 mei_mecbrw_read(struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CB_RW); +} + + +/* + * mei interface function prototypes + */ +void mei_hcsr_set(struct mei_device *dev); +void mei_csr_clear_his(struct mei_device *dev); + +void mei_enable_interrupts(struct mei_device *dev); +void mei_disable_interrupts(struct mei_device *dev); + +#endif diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c new file mode 100644 index 000000000000..6be5605707b4 --- /dev/null +++ b/drivers/misc/mei/wd.c @@ -0,0 +1,379 @@ +/* + * + * 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/pci.h> +#include <linux/sched.h> +#include <linux/watchdog.h> + +#include "mei_dev.h" +#include "hw.h" +#include "interface.h" +#include <linux/mei.h> + +static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; +static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 }; + +const u8 mei_wd_state_independence_msg[3][4] = { + {0x05, 0x02, 0x51, 0x10}, + {0x05, 0x02, 0x52, 0x10}, + {0x07, 0x02, 0x01, 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->pdev->dev, "wd: set timeout=%d.\n", timeout); + memcpy(dev->wd_data, mei_start_wd_params, MEI_WD_PARAMS_SIZE); + memcpy(dev->wd_data + MEI_WD_PARAMS_SIZE, &timeout, sizeof(u16)); +} + +/** + * host_init_wd - mei initialization wd. + * + * @dev: the device structure + * returns -ENENT if wd client cannot be found + * -EIO if write has failed + */ +int mei_wd_host_init(struct mei_device *dev) +{ + mei_cl_init(&dev->wd_cl, dev); + + /* look for WD client and connect to it */ + dev->wd_cl.state = MEI_FILE_DISCONNECTED; + dev->wd_timeout = AMT_WD_DEFAULT_TIMEOUT; + + /* find ME WD client */ + mei_find_me_client_update_filext(dev, &dev->wd_cl, + &mei_wd_guid, MEI_WD_HOST_CLIENT_ID); + + dev_dbg(&dev->pdev->dev, "wd: check client\n"); + if (MEI_FILE_CONNECTING != dev->wd_cl.state) { + dev_info(&dev->pdev->dev, "wd: failed to find the client\n"); + return -ENOENT; + } + + if (mei_connect(dev, &dev->wd_cl)) { + dev_err(&dev->pdev->dev, "wd: failed to connect to the client\n"); + dev->wd_cl.state = MEI_FILE_DISCONNECTED; + dev->wd_cl.host_client_id = 0; + return -EIO; + } + dev->wd_cl.timer_count = CONNECT_TIMEOUT; + + return 0; +} + +/** + * mei_wd_send - sends watch dog message to fw. + * + * @dev: the device structure + * + * returns 0 if success, + * -EIO when message send fails + * -EINVAL when invalid message is to be sent + */ +int mei_wd_send(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = dev->wd_cl.host_client_id; + mei_hdr->me_addr = dev->wd_cl.me_client_id; + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_PARAMS_SIZE)) + mei_hdr->length = MEI_START_WD_DATA_SIZE; + else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE)) + mei_hdr->length = MEI_WD_PARAMS_SIZE; + else + return -EINVAL; + + return mei_write_message(dev, mei_hdr, dev->wd_data, mei_hdr->length); +} + +/** + * mei_wd_stop - sends watchdog stop message to fw. + * + * @dev: the device structure + * @preserve: indicate if to keep the timeout value + * + * returns 0 if success, + * -EIO when message send fails + * -EINVAL when invalid message is to be sent + */ +int mei_wd_stop(struct mei_device *dev, bool preserve) +{ + int ret; + u16 wd_timeout = dev->wd_timeout; + + cancel_delayed_work(&dev->timer_work); + if (dev->wd_cl.state != MEI_FILE_CONNECTED || !dev->wd_timeout) + return 0; + + dev->wd_timeout = 0; + dev->wd_due_counter = 0; + memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE); + dev->stop = true; + + ret = mei_flow_ctrl_creds(dev, &dev->wd_cl); + if (ret < 0) + goto out; + + if (ret && dev->mei_host_buffer_is_empty) { + ret = 0; + dev->mei_host_buffer_is_empty = false; + + if (!mei_wd_send(dev)) { + ret = mei_flow_ctrl_reduce(dev, &dev->wd_cl); + if (ret) + goto out; + } else { + dev_err(&dev->pdev->dev, "wd: send stop failed\n"); + } + + dev->wd_pending = false; + } else { + dev->wd_pending = true; + } + dev->wd_stopped = false; + mutex_unlock(&dev->device_lock); + + ret = wait_event_interruptible_timeout(dev->wait_stop_wd, + dev->wd_stopped, 10 * HZ); + mutex_lock(&dev->device_lock); + if (dev->wd_stopped) { + dev_dbg(&dev->pdev->dev, "wd: stop completed ret=%d.\n", ret); + ret = 0; + } else { + if (!ret) + ret = -ETIMEDOUT; + dev_warn(&dev->pdev->dev, + "wd: stop failed to complete ret=%d.\n", ret); + } + + if (preserve) + dev->wd_timeout = wd_timeout; + +out: + return ret; +} + +/* + * mei_wd_ops_start - wd start command from the watchdog core. + * + * @wd_dev - watchdog device struct + * + * returns 0 if success, negative errno code for failure + */ +static int mei_wd_ops_start(struct watchdog_device *wd_dev) +{ + int err = -ENODEV; + struct mei_device *dev; + + dev = pci_get_drvdata(mei_device); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (dev->mei_state != MEI_ENABLED) { + dev_dbg(&dev->pdev->dev, + "wd: mei_state != MEI_ENABLED mei_state = %d\n", + dev->mei_state); + goto end_unlock; + } + + if (dev->wd_cl.state != MEI_FILE_CONNECTED) { + dev_dbg(&dev->pdev->dev, + "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 + * + * returns 0 if success, negative errno code for failure + */ +static int mei_wd_ops_stop(struct watchdog_device *wd_dev) +{ + struct mei_device *dev; + dev = pci_get_drvdata(mei_device); + + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + mei_wd_stop(dev, false); + mutex_unlock(&dev->device_lock); + + return 0; +} + +/* + * mei_wd_ops_ping - wd ping command from the watchdog core. + * + * @wd_dev - watchdog device struct + * + * returns 0 if success, negative errno code for failure + */ +static int mei_wd_ops_ping(struct watchdog_device *wd_dev) +{ + int ret = 0; + struct mei_device *dev; + dev = pci_get_drvdata(mei_device); + + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (dev->wd_cl.state != MEI_FILE_CONNECTED) { + dev_err(&dev->pdev->dev, "wd: not connected.\n"); + ret = -ENODEV; + goto end; + } + + /* Check if we can send the ping to HW*/ + if (dev->mei_host_buffer_is_empty && + mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { + + dev->mei_host_buffer_is_empty = false; + dev_dbg(&dev->pdev->dev, "wd: sending ping\n"); + + if (mei_wd_send(dev)) { + dev_err(&dev->pdev->dev, "wd: send failed.\n"); + ret = -EIO; + goto end; + } + + if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) { + dev_err(&dev->pdev->dev, + "wd: mei_flow_ctrl_reduce() failed.\n"); + ret = -EIO; + goto end; + } + + } 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 + * + * returns 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 = pci_get_drvdata(mei_device); + + if (!dev) + return -ENODEV; + + /* Check Timeout value */ + if (timeout < AMT_WD_MIN_TIMEOUT || timeout > AMT_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, +}; + +static struct watchdog_device amt_wd_dev = { + .info = &wd_info, + .ops = &wd_ops, + .timeout = AMT_WD_DEFAULT_TIMEOUT, + .min_timeout = AMT_WD_MIN_TIMEOUT, + .max_timeout = AMT_WD_MAX_TIMEOUT, +}; + + +void mei_watchdog_register(struct mei_device *dev) +{ + dev_dbg(&dev->pdev->dev, "dev->wd_timeout =%d.\n", dev->wd_timeout); + + dev->wd_due_counter = !!dev->wd_timeout; + + if (watchdog_register_device(&amt_wd_dev)) { + dev_err(&dev->pdev->dev, + "wd: unable to register watchdog device.\n"); + dev->wd_interface_reg = false; + } else { + dev_dbg(&dev->pdev->dev, + "wd: successfully register watchdog interface.\n"); + dev->wd_interface_reg = true; + } +} + +void mei_watchdog_unregister(struct mei_device *dev) +{ + if (dev->wd_interface_reg) + watchdog_unregister_device(&amt_wd_dev); + dev->wd_interface_reg = false; +} + diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c index 10fc4785dba7..9fbcacd703d5 100644 --- a/drivers/misc/pch_phub.c +++ b/drivers/misc/pch_phub.c @@ -65,10 +65,6 @@ #define PCI_VENDOR_ID_ROHM 0x10db #define PCI_DEVICE_ID_ROHM_ML7213_PHUB 0x801A -/* Macros for ML7213 */ -#define PCI_VENDOR_ID_ROHM 0x10db -#define PCI_DEVICE_ID_ROHM_ML7213_PHUB 0x801A - /* Macros for ML7223 */ #define PCI_DEVICE_ID_ROHM_ML7223_mPHUB 0x8012 /* for Bus-m */ #define PCI_DEVICE_ID_ROHM_ML7223_nPHUB 0x8002 /* for Bus-n */ diff --git a/drivers/misc/pti.c b/drivers/misc/pti.c index 383133b201a1..b7eb545394b1 100644 --- a/drivers/misc/pti.c +++ b/drivers/misc/pti.c @@ -888,7 +888,7 @@ static struct pci_driver pti_pci_driver = { .name = PCINAME, .id_table = pci_ids, .probe = pti_pci_probe, - .remove = pti_pci_remove, + .remove = __devexit_p(pti_pci_remove), }; /** |