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/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 | 
24 files changed, 6270 insertions, 696 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c7795096d43..2661f6e366f 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 3e1d80106f0..456972faaeb 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/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c index 1d1d4261591..6938f1be664 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 54f6f39f990..f1f9877f3fd 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 00000000000..9943971c13e --- /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 00000000000..78aaff9b523 --- /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 76c3064629f..62e418293b7 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; -} - -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), +	struct bmp085_data *data = dev_get_drvdata(dev); -	.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 00000000000..2b8f615bca9 --- /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 e46af9a5810..33ee834e1b8 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 01ab3c9b4cf..0842c2994ee 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 19591eaa492..00000000000 --- 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 00000000000..47d78a72db2 --- /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 00000000000..57168db6c7e --- /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 00000000000..24c4c962819 --- /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 00000000000..a7d0bb0880e --- /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 00000000000..428d21e3641 --- /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 00000000000..ddff5d16616 --- /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 00000000000..93936f1b75e --- /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 00000000000..f9cced69b65 --- /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 00000000000..c7033322833 --- /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 00000000000..63d7ee97c5f --- /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 00000000000..6be5605707b --- /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 10fc4785dba..9fbcacd703d 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 383133b201a..b7eb545394b 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),  };  /**  |