diff options
Diffstat (limited to 'drivers/misc')
74 files changed, 6702 insertions, 3272 deletions
| diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 054fc10cb3b6..a216b4667742 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -440,7 +440,7 @@ config ARM_CHARLCD  	  still useful.  config BMP085 -	bool +	tristate  	depends on SYSFS  config BMP085_I2C @@ -470,7 +470,7 @@ config BMP085_SPI  config PCH_PHUB  	tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"  	select GENERIC_NET_UTILS -	depends on PCI && (X86_32 || COMPILE_TEST) +	depends on PCI && (X86_32 || MIPS || COMPILE_TEST)  	help  	  This driver is for PCH(Platform controller Hub) PHUB(Packet Hub) of  	  Intel Topcliff which is an IOH(Input/Output Hub) for x86 embedded @@ -525,6 +525,284 @@ config VEXPRESS_SYSCFG  	  ARM Ltd. Versatile Express uses specialised platform configuration  	  bus. System Configuration interface is one of the possible means  	  of generating transactions on this bus. +config PANEL +	tristate "Parallel port LCD/Keypad Panel support" +	depends on PARPORT +	---help--- +	  Say Y here if you have an HD44780 or KS-0074 LCD connected to your +	  parallel port. This driver also features 4 and 6-key keypads. The LCD +	  is accessible through the /dev/lcd char device (10, 156), and the +	  keypad through /dev/keypad (10, 185). Both require misc device to be +	  enabled. This code can either be compiled as a module, or linked into +	  the kernel and started at boot. If you don't understand what all this +	  is about, say N. + +config PANEL_PARPORT +	int "Default parallel port number (0=LPT1)" +	depends on PANEL +	range 0 255 +	default "0" +	---help--- +	  This is the index of the parallel port the panel is connected to. One +	  driver instance only supports one parallel port, so if your keypad +	  and LCD are connected to two separate ports, you have to start two +	  modules with different arguments. Numbering starts with '0' for LPT1, +	  and so on. + +config PANEL_PROFILE +	int "Default panel profile (0-5, 0=custom)" +	depends on PANEL +	range 0 5 +	default "5" +	---help--- +	  To ease configuration, the driver supports different configuration +	  profiles for past and recent wirings. These profiles can also be +	  used to define an approximative configuration, completed by a few +	  other options. Here are the profiles : + +	    0 = custom (see further) +	    1 = 2x16 parallel LCD, old keypad +	    2 = 2x16 serial LCD (KS-0074), new keypad +	    3 = 2x16 parallel LCD (Hantronix), no keypad +	    4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad +	    5 = 2x40 parallel LCD (old one), with old keypad + +	  Custom configurations allow you to define how your display is +	  wired to the parallel port, and how it works. This is only intended +	  for experts. + +config PANEL_KEYPAD +	depends on PANEL && PANEL_PROFILE="0" +	int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)" +	range 0 3 +	default 0 +	---help--- +	  This enables and configures a keypad connected to the parallel port. +	  The keys will be read from character device 10,185. Valid values are : + +	    0 : do not enable this driver +	    1 : old 6 keys keypad +	    2 : new 6 keys keypad, as used on the server at www.ant-computing.com +	    3 : Nexcom NSA1045's 4 keys keypad + +	  New profiles can be described in the driver source. The driver also +	  supports simultaneous keys pressed when the keypad supports them. + +config PANEL_LCD +	depends on PANEL && PANEL_PROFILE="0" +	int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)" +	range 0 5 +	default 0 +	---help--- +	   This enables and configures an LCD connected to the parallel port. +	   The driver includes an interpreter for escape codes starting with +	   '\e[L' which are specific to the LCD, and a few ANSI codes. The +	   driver will be registered as character device 10,156, usually +	   under the name '/dev/lcd'. There are a total of 6 supported types : + +	     0 : do not enable the driver +	     1 : custom configuration and wiring (see further) +	     2 : 2x16 & 2x40 parallel LCD (old wiring) +	     3 : 2x16 serial LCD (KS-0074 based) +	     4 : 2x16 parallel LCD (Hantronix wiring) +	     5 : 2x16 parallel LCD (Nexcom wiring) + +	   When type '1' is specified, other options will appear to configure +	   more precise aspects (wiring, dimensions, protocol, ...). Please note +	   that those values changed from the 2.4 driver for better consistency. + +config PANEL_LCD_HEIGHT +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" +	int "Number of lines on the LCD (1-2)" +	range 1 2 +	default 2 +	---help--- +	  This is the number of visible character lines on the LCD in custom profile. +	  It can either be 1 or 2. + +config PANEL_LCD_WIDTH +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" +	int "Number of characters per line on the LCD (1-40)" +	range 1 40 +	default 40 +	---help--- +	  This is the number of characters per line on the LCD in custom profile. +	  Common values are 16,20,24,40. + +config PANEL_LCD_BWIDTH +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" +	int "Internal LCD line width (1-40, 40 by default)" +	range 1 40 +	default 40 +	---help--- +	  Most LCDs use a standard controller which supports hardware lines of 40 +	  characters, although sometimes only 16, 20 or 24 of them are really wired +	  to the terminal. This results in some non-visible but addressable characters, +	  and is the case for most parallel LCDs. Other LCDs, and some serial ones, +	  however, use the same line width internally as what is visible. The KS0074 +	  for example, uses 16 characters per line for 16 visible characters per line. + +	  This option lets you configure the value used by your LCD in 'custom' profile. +	  If you don't know, put '40' here. + +config PANEL_LCD_HWIDTH +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" +	int "Hardware LCD line width (1-64, 64 by default)" +	range 1 64 +	default 64 +	---help--- +	  Most LCDs use a single address bit to differentiate line 0 and line 1. Since +	  some of them need to be able to address 40 chars with the lower bits, they +	  often use the immediately superior power of 2, which is 64, to address the +	  next line. + +	  If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and +	  64 here for a 2x40. + +config PANEL_LCD_CHARSET +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" +	int "LCD character set (0=normal, 1=KS0074)" +	range 0 1 +	default 0 +	---help--- +	  Some controllers such as the KS0074 use a somewhat strange character set +	  where many symbols are at unusual places. The driver knows how to map +	  'standard' ASCII characters to the character sets used by these controllers. +	  Valid values are : + +	     0 : normal (untranslated) character set +	     1 : KS0074 character set + +	  If you don't know, use the normal one (0). + +config PANEL_LCD_PROTO +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" +	int "LCD communication mode (0=parallel 8 bits, 1=serial)" +	range 0 1 +	default 0 +	---help--- +	  This driver now supports any serial or parallel LCD wired to a parallel +	  port. But before assigning signals, the driver needs to know if it will +	  be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires +	  (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals +	  (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits +	  parallel LCD, and 1 for a serial LCD. + +config PANEL_LCD_PIN_E +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" +        int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) " +	range -17 17 +	default 14 +	---help--- +	  This describes the number of the parallel port pin to which the LCD 'E' +	  signal has been connected. It can be : + +	          0 : no connection (eg: connected to ground) +	      1..17 : directly connected to any of these pins on the DB25 plug +	    -1..-17 : connected to the same pin through an inverter (eg: transistor). + +	  Default for the 'E' pin in custom profile is '14' (AUTOFEED). + +config PANEL_LCD_PIN_RS +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" +        int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) " +	range -17 17 +	default 17 +	---help--- +	  This describes the number of the parallel port pin to which the LCD 'RS' +	  signal has been connected. It can be : + +	          0 : no connection (eg: connected to ground) +	      1..17 : directly connected to any of these pins on the DB25 plug +	    -1..-17 : connected to the same pin through an inverter (eg: transistor). + +	  Default for the 'RS' pin in custom profile is '17' (SELECT IN). + +config PANEL_LCD_PIN_RW +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" +        int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) " +	range -17 17 +	default 16 +	---help--- +	  This describes the number of the parallel port pin to which the LCD 'RW' +	  signal has been connected. It can be : + +	          0 : no connection (eg: connected to ground) +	      1..17 : directly connected to any of these pins on the DB25 plug +	    -1..-17 : connected to the same pin through an inverter (eg: transistor). + +	  Default for the 'RW' pin in custom profile is '16' (INIT). + +config PANEL_LCD_PIN_SCL +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" +        int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) " +	range -17 17 +	default 1 +	---help--- +	  This describes the number of the parallel port pin to which the serial +	  LCD 'SCL' signal has been connected. It can be : + +	          0 : no connection (eg: connected to ground) +	      1..17 : directly connected to any of these pins on the DB25 plug +	    -1..-17 : connected to the same pin through an inverter (eg: transistor). + +	  Default for the 'SCL' pin in custom profile is '1' (STROBE). + +config PANEL_LCD_PIN_SDA +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" +        int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) " +	range -17 17 +	default 2 +	---help--- +	  This describes the number of the parallel port pin to which the serial +	  LCD 'SDA' signal has been connected. It can be : + +	          0 : no connection (eg: connected to ground) +	      1..17 : directly connected to any of these pins on the DB25 plug +	    -1..-17 : connected to the same pin through an inverter (eg: transistor). + +	  Default for the 'SDA' pin in custom profile is '2' (D0). + +config PANEL_LCD_PIN_BL +	depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" +        int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) " +	range -17 17 +	default 0 +	---help--- +	  This describes the number of the parallel port pin to which the LCD 'BL' signal +          has been connected. It can be : + +	          0 : no connection (eg: connected to ground) +	      1..17 : directly connected to any of these pins on the DB25 plug +	    -1..-17 : connected to the same pin through an inverter (eg: transistor). + +	  Default for the 'BL' pin in custom profile is '0' (uncontrolled). + +config PANEL_CHANGE_MESSAGE +	depends on PANEL +	bool "Change LCD initialization message ?" +	default "n" +	---help--- +	  This allows you to replace the boot message indicating the kernel version +	  and the driver version with a custom message. This is useful on appliances +	  where a simple 'Starting system' message can be enough to stop a customer +	  from worrying. + +	  If you say 'Y' here, you'll be able to choose a message yourself. Otherwise, +	  say 'N' and keep the default message with the version. + +config PANEL_BOOT_MESSAGE +	depends on PANEL && PANEL_CHANGE_MESSAGE="y" +	string "New initialization message" +	default "" +	---help--- +	  This allows you to replace the boot message indicating the kernel version +	  and the driver version with a custom message. This is useful on appliances +	  where a simple 'Starting system' message can be enough to stop a customer +	  from worrying. + +	  An empty message will only clear the display at driver init time. Any other +	  printf()-formatted message is valid with newline and escape codes.  source "drivers/misc/c2port/Kconfig"  source "drivers/misc/eeprom/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 537d7f3b78da..b2fb6dbffcef 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE)		+= genwqe/  obj-$(CONFIG_ECHO)		+= echo/  obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o  obj-$(CONFIG_CXL_BASE)		+= cxl/ +obj-$(CONFIG_PANEL)             += panel.o diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c index 15e88078ba1e..fe1672747bc1 100644 --- a/drivers/misc/ad525x_dpot.c +++ b/drivers/misc/ad525x_dpot.c @@ -216,7 +216,7 @@ static s32 dpot_read_i2c(struct dpot_data *dpot, u8 reg)  			 */  			value = swab16(value); -			if (dpot->uid == DPOT_UID(AD5271_ID)) +			if (dpot->uid == DPOT_UID(AD5274_ID))  				value = value >> 2;  		return value;  	default: @@ -452,7 +452,7 @@ static ssize_t sysfs_set_reg(struct device *dev,  	int err;  	if (reg & DPOT_ADDR_OTP_EN) { -		if (!strncmp(buf, "enabled", sizeof("enabled"))) +		if (sysfs_streq(buf, "enabled"))  			set_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask);  		else  			clear_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask); diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c index a3e789b85cc8..dfb72ecfa604 100644 --- a/drivers/misc/apds990x.c +++ b/drivers/misc/apds990x.c @@ -1215,7 +1215,7 @@ static int apds990x_remove(struct i2c_client *client)  #ifdef CONFIG_PM_SLEEP  static int apds990x_suspend(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct apds990x_chip *chip = i2c_get_clientdata(client);  	apds990x_chip_off(chip); @@ -1224,7 +1224,7 @@ static int apds990x_suspend(struct device *dev)  static int apds990x_resume(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct apds990x_chip *chip = i2c_get_clientdata(client);  	/* @@ -1240,7 +1240,7 @@ static int apds990x_resume(struct device *dev)  #ifdef CONFIG_PM  static int apds990x_runtime_suspend(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct apds990x_chip *chip = i2c_get_clientdata(client);  	apds990x_chip_off(chip); @@ -1249,7 +1249,7 @@ static int apds990x_runtime_suspend(struct device *dev)  static int apds990x_runtime_resume(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct apds990x_chip *chip = i2c_get_clientdata(client);  	apds990x_chip_on(chip); diff --git a/drivers/misc/arm-charlcd.c b/drivers/misc/arm-charlcd.c index c65b5ea5d5ef..b3176ee92b90 100644 --- a/drivers/misc/arm-charlcd.c +++ b/drivers/misc/arm-charlcd.c @@ -8,7 +8,6 @@   * Author: Linus Walleij <triad@df.lth.se>   */  #include <linux/init.h> -#include <linux/module.h>  #include <linux/interrupt.h>  #include <linux/platform_device.h>  #include <linux/of.h> @@ -328,20 +327,6 @@ out_no_resource:  	return ret;  } -static int __exit charlcd_remove(struct platform_device *pdev) -{ -	struct charlcd *lcd = platform_get_drvdata(pdev); - -	if (lcd) { -		free_irq(lcd->irq, lcd); -		iounmap(lcd->virtbase); -		release_mem_region(lcd->phybase, lcd->physize); -		kfree(lcd); -	} - -	return 0; -} -  static int charlcd_suspend(struct device *dev)  {  	struct platform_device *pdev = to_platform_device(dev); @@ -376,13 +361,8 @@ static struct platform_driver charlcd_driver = {  	.driver = {  		.name = DRIVERNAME,  		.pm = &charlcd_pm_ops, +		.suppress_bind_attrs = true,  		.of_match_table = of_match_ptr(charlcd_match),  	}, -	.remove = __exit_p(charlcd_remove),  }; - -module_platform_driver_probe(charlcd_driver, charlcd_probe); - -MODULE_AUTHOR("Linus Walleij <triad@df.lth.se>"); -MODULE_DESCRIPTION("ARM Character LCD Driver"); -MODULE_LICENSE("GPL v2"); +builtin_platform_driver_probe(charlcd_driver, charlcd_probe); diff --git a/drivers/misc/atmel-ssc.c b/drivers/misc/atmel-ssc.c index e11a0bd6c66e..0516ecda54d3 100644 --- a/drivers/misc/atmel-ssc.c +++ b/drivers/misc/atmel-ssc.c @@ -34,6 +34,7 @@ struct ssc_device *ssc_request(unsigned int ssc_num)  		if (ssc->pdev->dev.of_node) {  			if (of_alias_get_id(ssc->pdev->dev.of_node, "ssc")  				== ssc_num) { +				ssc->pdev->id = ssc_num;  				ssc_valid = 1;  				break;  			} diff --git a/drivers/misc/bh1770glc.c b/drivers/misc/bh1770glc.c index 753d7ecdadaa..845466e45b95 100644 --- a/drivers/misc/bh1770glc.c +++ b/drivers/misc/bh1770glc.c @@ -1323,7 +1323,7 @@ static int bh1770_remove(struct i2c_client *client)  #ifdef CONFIG_PM_SLEEP  static int bh1770_suspend(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct bh1770_chip *chip = i2c_get_clientdata(client);  	bh1770_chip_off(chip); @@ -1333,7 +1333,7 @@ static int bh1770_suspend(struct device *dev)  static int bh1770_resume(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct bh1770_chip *chip = i2c_get_clientdata(client);  	int ret = 0; @@ -1361,7 +1361,7 @@ static int bh1770_resume(struct device *dev)  #ifdef CONFIG_PM  static int bh1770_runtime_suspend(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct bh1770_chip *chip = i2c_get_clientdata(client);  	bh1770_chip_off(chip); @@ -1371,7 +1371,7 @@ static int bh1770_runtime_suspend(struct device *dev)  static int bh1770_runtime_resume(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct bh1770_chip *chip = i2c_get_clientdata(client);  	bh1770_chip_on(chip); diff --git a/drivers/misc/c2port/core.c b/drivers/misc/c2port/core.c index cc8645b5369d..1922cb8f6b88 100644 --- a/drivers/misc/c2port/core.c +++ b/drivers/misc/c2port/core.c @@ -721,9 +721,7 @@ static ssize_t c2port_read_flash_data(struct file *filp, struct kobject *kobj,  				struct bin_attribute *attr,  				char *buffer, loff_t offset, size_t count)  { -	struct c2port_device *c2dev = -			dev_get_drvdata(container_of(kobj, -						struct device, kobj)); +	struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj));  	ssize_t ret;  	/* Check the device and flash access status */ @@ -838,9 +836,7 @@ static ssize_t c2port_write_flash_data(struct file *filp, struct kobject *kobj,  				struct bin_attribute *attr,  				char *buffer, loff_t offset, size_t count)  { -	struct c2port_device *c2dev = -			dev_get_drvdata(container_of(kobj, -						struct device, kobj)); +	struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj));  	int ret;  	/* Check the device access status */ diff --git a/drivers/misc/cxl/pci.c b/drivers/misc/cxl/pci.c index 4c1903f781fc..a89608334ed5 100644 --- a/drivers/misc/cxl/pci.c +++ b/drivers/misc/cxl/pci.c @@ -19,7 +19,6 @@  #include <linux/delay.h>  #include <asm/opal.h>  #include <asm/msi_bitmap.h> -#include <asm/pci-bridge.h> /* for struct pci_controller */  #include <asm/pnv-pci.h>  #include <asm/io.h> @@ -415,7 +414,7 @@ static int cxl_setup_psl_timebase(struct cxl *adapter, struct pci_dev *dev)  		delta = mftb() - psl_tb;  		if (delta < 0)  			delta = -delta; -	} while (cputime_to_usecs(delta) > 16); +	} while (tb_to_ns(delta) > 16000);  	return 0;  } diff --git a/drivers/misc/cxl/sysfs.c b/drivers/misc/cxl/sysfs.c index 02006f7109a8..038af5d45145 100644 --- a/drivers/misc/cxl/sysfs.c +++ b/drivers/misc/cxl/sysfs.c @@ -386,8 +386,7 @@ static ssize_t afu_eb_read(struct file *filp, struct kobject *kobj,  			       struct bin_attribute *bin_attr, char *buf,  			       loff_t off, size_t count)  { -	struct cxl_afu *afu = to_cxl_afu(container_of(kobj, -						      struct device, kobj)); +	struct cxl_afu *afu = to_cxl_afu(kobj_to_dev(kobj));  	return cxl_afu_read_err_buffer(afu, buf, off, count);  } @@ -467,7 +466,7 @@ static ssize_t afu_read_config(struct file *filp, struct kobject *kobj,  			       loff_t off, size_t count)  {  	struct afu_config_record *cr = to_cr(kobj); -	struct cxl_afu *afu = to_cxl_afu(container_of(kobj->parent, struct device, kobj)); +	struct cxl_afu *afu = to_cxl_afu(kobj_to_dev(kobj->parent));  	u64 i, j, val; diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 04f2e1fa9dd1..cfc493c2e30a 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -3,6 +3,8 @@ menu "EEPROM support"  config EEPROM_AT24  	tristate "I2C EEPROMs / RAMs / ROMs from most vendors"  	depends on I2C && SYSFS +	select REGMAP +	select NVMEM  	help  	  Enable this driver to get read/write support to most I2C EEPROMs  	  and compatible devices like FRAMs, SRAMs, ROMs etc. After you @@ -30,6 +32,8 @@ config EEPROM_AT24  config EEPROM_AT25  	tristate "SPI EEPROMs from most vendors"  	depends on SPI && SYSFS +	select REGMAP +	select NVMEM  	help  	  Enable this driver to get read/write support to most SPI EEPROMs,  	  after you configure the board init code to know about each eeprom @@ -74,6 +78,8 @@ config EEPROM_93CX6  config EEPROM_93XX46  	tristate "Microwire EEPROM 93XX46 support"  	depends on SPI && SYSFS +	select REGMAP +	select NVMEM  	help  	  Driver for the microwire EEPROM chipsets 93xx46x. The driver  	  supports both read and write commands and also the command to diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 5d7c0900fa1b..089d6943f68a 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -15,7 +15,6 @@  #include <linux/slab.h>  #include <linux/delay.h>  #include <linux/mutex.h> -#include <linux/sysfs.h>  #include <linux/mod_devicetable.h>  #include <linux/log2.h>  #include <linux/bitops.h> @@ -23,6 +22,8 @@  #include <linux/of.h>  #include <linux/acpi.h>  #include <linux/i2c.h> +#include <linux/nvmem-provider.h> +#include <linux/regmap.h>  #include <linux/platform_data/at24.h>  /* @@ -55,7 +56,6 @@  struct at24_data {  	struct at24_platform_data chip; -	struct memory_accessor macc;  	int use_smbus;  	int use_smbus_write; @@ -64,12 +64,15 @@ struct at24_data {  	 * but not from changes by other I2C masters.  	 */  	struct mutex lock; -	struct bin_attribute bin;  	u8 *writebuf;  	unsigned write_max;  	unsigned num_addresses; +	struct regmap_config regmap_config; +	struct nvmem_config nvmem_config; +	struct nvmem_device *nvmem; +  	/*  	 * Some chips tie up multiple I2C addresses; dummy devices reserve  	 * them for us, and we'll use them with SMBus calls. @@ -283,17 +286,6 @@ static ssize_t at24_read(struct at24_data *at24,  	return retval;  } -static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj, -		struct bin_attribute *attr, -		char *buf, loff_t off, size_t count) -{ -	struct at24_data *at24; - -	at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); -	return at24_read(at24, buf, off, count); -} - -  /*   * Note that if the hardware write-protect pin is pulled high, the whole   * chip is normally write protected. But there are plenty of product @@ -414,40 +406,49 @@ static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,  	return retval;  } -static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj, -		struct bin_attribute *attr, -		char *buf, loff_t off, size_t count) -{ -	struct at24_data *at24; - -	at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); -	return at24_write(at24, buf, off, count); -} -  /*-------------------------------------------------------------------------*/  /* - * This lets other kernel code access the eeprom data. For example, it - * might hold a board's Ethernet address, or board-specific calibration - * data generated on the manufacturing floor. - */ - -static ssize_t at24_macc_read(struct memory_accessor *macc, char *buf, -			 off_t offset, size_t count) + * Provide a regmap interface, which is registered with the NVMEM + * framework +*/ +static int at24_regmap_read(void *context, const void *reg, size_t reg_size, +			    void *val, size_t val_size)  { -	struct at24_data *at24 = container_of(macc, struct at24_data, macc); +	struct at24_data *at24 = context; +	off_t offset = *(u32 *)reg; +	int err; -	return at24_read(at24, buf, offset, count); +	err = at24_read(at24, val, offset, val_size); +	if (err) +		return err; +	return 0;  } -static ssize_t at24_macc_write(struct memory_accessor *macc, const char *buf, -			  off_t offset, size_t count) +static int at24_regmap_write(void *context, const void *data, size_t count)  { -	struct at24_data *at24 = container_of(macc, struct at24_data, macc); +	struct at24_data *at24 = context; +	const char *buf; +	u32 offset; +	size_t len; +	int err; -	return at24_write(at24, buf, offset, count); +	memcpy(&offset, data, sizeof(offset)); +	buf = (const char *)data + sizeof(offset); +	len = count - sizeof(offset); + +	err = at24_write(at24, buf, offset, len); +	if (err) +		return err; +	return 0;  } +static const struct regmap_bus at24_regmap_bus = { +	.read = at24_regmap_read, +	.write = at24_regmap_write, +	.reg_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; +  /*-------------------------------------------------------------------------*/  #ifdef CONFIG_OF @@ -481,6 +482,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  	struct at24_data *at24;  	int err;  	unsigned i, num_addresses; +	struct regmap *regmap;  	if (client->dev.platform_data) {  		chip = *(struct at24_platform_data *)client->dev.platform_data; @@ -573,29 +575,12 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  	at24->chip = chip;  	at24->num_addresses = num_addresses; -	/* -	 * Export the EEPROM bytes through sysfs, since that's convenient. -	 * By default, only root should see the data (maybe passwords etc) -	 */ -	sysfs_bin_attr_init(&at24->bin); -	at24->bin.attr.name = "eeprom"; -	at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR; -	at24->bin.read = at24_bin_read; -	at24->bin.size = chip.byte_len; - -	at24->macc.read = at24_macc_read; -  	writable = !(chip.flags & AT24_FLAG_READONLY);  	if (writable) {  		if (!use_smbus || use_smbus_write) {  			unsigned write_max = chip.page_size; -			at24->macc.write = at24_macc_write; - -			at24->bin.write = at24_bin_write; -			at24->bin.attr.mode |= S_IWUSR; -  			if (write_max > io_limit)  				write_max = io_limit;  			if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX) @@ -627,14 +612,38 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  		}  	} -	err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin); -	if (err) +	at24->regmap_config.reg_bits = 32; +	at24->regmap_config.val_bits = 8; +	at24->regmap_config.reg_stride = 1; +	at24->regmap_config.max_register = chip.byte_len - 1; + +	regmap = devm_regmap_init(&client->dev, &at24_regmap_bus, at24, +				  &at24->regmap_config); +	if (IS_ERR(regmap)) { +		dev_err(&client->dev, "regmap init failed\n"); +		err = PTR_ERR(regmap); +		goto err_clients; +	} + +	at24->nvmem_config.name = dev_name(&client->dev); +	at24->nvmem_config.dev = &client->dev; +	at24->nvmem_config.read_only = !writable; +	at24->nvmem_config.root_only = true; +	at24->nvmem_config.owner = THIS_MODULE; +	at24->nvmem_config.compat = true; +	at24->nvmem_config.base_dev = &client->dev; + +	at24->nvmem = nvmem_register(&at24->nvmem_config); + +	if (IS_ERR(at24->nvmem)) { +		err = PTR_ERR(at24->nvmem);  		goto err_clients; +	}  	i2c_set_clientdata(client, at24); -	dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n", -		at24->bin.size, client->name, +	dev_info(&client->dev, "%u byte %s EEPROM, %s, %u bytes/write\n", +		chip.byte_len, client->name,  		writable ? "writable" : "read-only", at24->write_max);  	if (use_smbus == I2C_SMBUS_WORD_DATA ||  	    use_smbus == I2C_SMBUS_BYTE_DATA) { @@ -645,7 +654,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  	/* export data to kernel code */  	if (chip.setup) -		chip.setup(&at24->macc, chip.context); +		chip.setup(at24->nvmem, chip.context);  	return 0; @@ -663,7 +672,8 @@ static int at24_remove(struct i2c_client *client)  	int i;  	at24 = i2c_get_clientdata(client); -	sysfs_remove_bin_file(&client->dev.kobj, &at24->bin); + +	nvmem_unregister(at24->nvmem);  	for (i = 1; i < at24->num_addresses; i++)  		i2c_unregister_device(at24->client[i]); diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index f850ef556bcc..fa36a6e37084 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -16,6 +16,8 @@  #include <linux/device.h>  #include <linux/sched.h> +#include <linux/nvmem-provider.h> +#include <linux/regmap.h>  #include <linux/spi/spi.h>  #include <linux/spi/eeprom.h>  #include <linux/property.h> @@ -29,11 +31,12 @@  struct at25_data {  	struct spi_device	*spi; -	struct memory_accessor	mem;  	struct mutex		lock;  	struct spi_eeprom	chip; -	struct bin_attribute	bin;  	unsigned		addrlen; +	struct regmap_config	regmap_config; +	struct nvmem_config	nvmem_config; +	struct nvmem_device	*nvmem;  };  #define	AT25_WREN	0x06		/* latch the write enable */ @@ -77,10 +80,10 @@ at25_ee_read(  	struct spi_message	m;  	u8			instr; -	if (unlikely(offset >= at25->bin.size)) +	if (unlikely(offset >= at25->chip.byte_len))  		return 0; -	if ((offset + count) > at25->bin.size) -		count = at25->bin.size - offset; +	if ((offset + count) > at25->chip.byte_len) +		count = at25->chip.byte_len - offset;  	if (unlikely(!count))  		return count; @@ -131,21 +134,19 @@ at25_ee_read(  	return status ? status : count;  } -static ssize_t -at25_bin_read(struct file *filp, struct kobject *kobj, -	      struct bin_attribute *bin_attr, -	      char *buf, loff_t off, size_t count) +static int at25_regmap_read(void *context, const void *reg, size_t reg_size, +			    void *val, size_t val_size)  { -	struct device		*dev; -	struct at25_data	*at25; +	struct at25_data *at25 = context; +	off_t offset = *(u32 *)reg; +	int err; -	dev = container_of(kobj, struct device, kobj); -	at25 = dev_get_drvdata(dev); - -	return at25_ee_read(at25, buf, off, count); +	err = at25_ee_read(at25, val, offset, val_size); +	if (err) +		return err; +	return 0;  } -  static ssize_t  at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,  	      size_t count) @@ -155,10 +156,10 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,  	unsigned		buf_size;  	u8			*bounce; -	if (unlikely(off >= at25->bin.size)) +	if (unlikely(off >= at25->chip.byte_len))  		return -EFBIG; -	if ((off + count) > at25->bin.size) -		count = at25->bin.size - off; +	if ((off + count) > at25->chip.byte_len) +		count = at25->chip.byte_len - off;  	if (unlikely(!count))  		return count; @@ -265,39 +266,29 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,  	return written ? written : status;  } -static ssize_t -at25_bin_write(struct file *filp, struct kobject *kobj, -	       struct bin_attribute *bin_attr, -	       char *buf, loff_t off, size_t count) +static int at25_regmap_write(void *context, const void *data, size_t count)  { -	struct device		*dev; -	struct at25_data	*at25; - -	dev = container_of(kobj, struct device, kobj); -	at25 = dev_get_drvdata(dev); - -	return at25_ee_write(at25, buf, off, count); -} +	struct at25_data *at25 = context; +	const char *buf; +	u32 offset; +	size_t len; +	int err; -/*-------------------------------------------------------------------------*/ - -/* Let in-kernel code access the eeprom data. */ - -static ssize_t at25_mem_read(struct memory_accessor *mem, char *buf, -			 off_t offset, size_t count) -{ -	struct at25_data *at25 = container_of(mem, struct at25_data, mem); +	memcpy(&offset, data, sizeof(offset)); +	buf = (const char *)data + sizeof(offset); +	len = count - sizeof(offset); -	return at25_ee_read(at25, buf, offset, count); +	err = at25_ee_write(at25, buf, offset, len); +	if (err) +		return err; +	return 0;  } -static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf, -			  off_t offset, size_t count) -{ -	struct at25_data *at25 = container_of(mem, struct at25_data, mem); - -	return at25_ee_write(at25, buf, offset, count); -} +static const struct regmap_bus at25_regmap_bus = { +	.read = at25_regmap_read, +	.write = at25_regmap_write, +	.reg_format_endian_default = REGMAP_ENDIAN_NATIVE, +};  /*-------------------------------------------------------------------------*/ @@ -358,6 +349,7 @@ static int at25_probe(struct spi_device *spi)  {  	struct at25_data	*at25 = NULL;  	struct spi_eeprom	chip; +	struct regmap		*regmap;  	int			err;  	int			sr;  	int			addrlen; @@ -402,40 +394,35 @@ static int at25_probe(struct spi_device *spi)  	spi_set_drvdata(spi, at25);  	at25->addrlen = addrlen; -	/* Export the EEPROM bytes through sysfs, since that's convenient. -	 * And maybe to other kernel code; it might hold a board's Ethernet -	 * address, or board-specific calibration data generated on the -	 * manufacturing floor. -	 * -	 * Default to root-only access to the data; EEPROMs often hold data -	 * that's sensitive for read and/or write, like ethernet addresses, -	 * security codes, board-specific manufacturing calibrations, etc. -	 */ -	sysfs_bin_attr_init(&at25->bin); -	at25->bin.attr.name = "eeprom"; -	at25->bin.attr.mode = S_IRUSR; -	at25->bin.read = at25_bin_read; -	at25->mem.read = at25_mem_read; - -	at25->bin.size = at25->chip.byte_len; -	if (!(chip.flags & EE_READONLY)) { -		at25->bin.write = at25_bin_write; -		at25->bin.attr.mode |= S_IWUSR; -		at25->mem.write = at25_mem_write; -	} +	at25->regmap_config.reg_bits = 32; +	at25->regmap_config.val_bits = 8; +	at25->regmap_config.reg_stride = 1; +	at25->regmap_config.max_register = chip.byte_len - 1; -	err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin); -	if (err) -		return err; - -	if (chip.setup) -		chip.setup(&at25->mem, chip.context); +	regmap = devm_regmap_init(&spi->dev, &at25_regmap_bus, at25, +				  &at25->regmap_config); +	if (IS_ERR(regmap)) { +		dev_err(&spi->dev, "regmap init failed\n"); +		return PTR_ERR(regmap); +	} -	dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n", -		(at25->bin.size < 1024) -			? at25->bin.size -			: (at25->bin.size / 1024), -		(at25->bin.size < 1024) ? "Byte" : "KByte", +	at25->nvmem_config.name = dev_name(&spi->dev); +	at25->nvmem_config.dev = &spi->dev; +	at25->nvmem_config.read_only = chip.flags & EE_READONLY; +	at25->nvmem_config.root_only = true; +	at25->nvmem_config.owner = THIS_MODULE; +	at25->nvmem_config.compat = true; +	at25->nvmem_config.base_dev = &spi->dev; + +	at25->nvmem = nvmem_register(&at25->nvmem_config); +	if (IS_ERR(at25->nvmem)) +		return PTR_ERR(at25->nvmem); + +	dev_info(&spi->dev, "%d %s %s eeprom%s, pagesize %u\n", +		(chip.byte_len < 1024) +			? chip.byte_len +			: (chip.byte_len / 1024), +		(chip.byte_len < 1024) ? "Byte" : "KByte",  		at25->chip.name,  		(chip.flags & EE_READONLY) ? " (readonly)" : "",  		at25->chip.page_size); @@ -447,7 +434,8 @@ static int at25_remove(struct spi_device *spi)  	struct at25_data	*at25;  	at25 = spi_get_drvdata(spi); -	sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin); +	nvmem_unregister(at25->nvmem); +  	return 0;  } diff --git a/drivers/misc/eeprom/eeprom.c b/drivers/misc/eeprom/eeprom.c index 7342fd637031..3d1d55157e5f 100644 --- a/drivers/misc/eeprom/eeprom.c +++ b/drivers/misc/eeprom/eeprom.c @@ -84,7 +84,7 @@ static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,  			   struct bin_attribute *bin_attr,  			   char *buf, loff_t off, size_t count)  { -	struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj)); +	struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj));  	struct eeprom_data *data = i2c_get_clientdata(client);  	u8 slice; diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c index ff63f05edc76..426fe2fd5238 100644 --- a/drivers/misc/eeprom/eeprom_93xx46.c +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -10,12 +10,17 @@  #include <linux/delay.h>  #include <linux/device.h> +#include <linux/gpio/consumer.h>  #include <linux/kernel.h>  #include <linux/module.h>  #include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h>  #include <linux/slab.h>  #include <linux/spi/spi.h> -#include <linux/sysfs.h> +#include <linux/nvmem-provider.h> +#include <linux/regmap.h>  #include <linux/eeprom_93xx46.h>  #define OP_START	0x4 @@ -25,73 +30,111 @@  #define ADDR_ERAL	0x20  #define ADDR_EWEN	0x30 +struct eeprom_93xx46_devtype_data { +	unsigned int quirks; +}; + +static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = { +	.quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ | +		  EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH, +}; +  struct eeprom_93xx46_dev {  	struct spi_device *spi;  	struct eeprom_93xx46_platform_data *pdata; -	struct bin_attribute bin;  	struct mutex lock; +	struct regmap_config regmap_config; +	struct nvmem_config nvmem_config; +	struct nvmem_device *nvmem;  	int addrlen; +	int size;  }; +static inline bool has_quirk_single_word_read(struct eeprom_93xx46_dev *edev) +{ +	return edev->pdata->quirks & EEPROM_93XX46_QUIRK_SINGLE_WORD_READ; +} + +static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev) +{ +	return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH; +} +  static ssize_t -eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj, -		       struct bin_attribute *bin_attr, -		       char *buf, loff_t off, size_t count) +eeprom_93xx46_read(struct eeprom_93xx46_dev *edev, char *buf, +		   unsigned off, size_t count)  { -	struct eeprom_93xx46_dev *edev; -	struct device *dev; -	struct spi_message m; -	struct spi_transfer t[2]; -	int bits, ret; -	u16 cmd_addr; +	ssize_t ret = 0; -	dev = container_of(kobj, struct device, kobj); -	edev = dev_get_drvdata(dev); +	if (unlikely(off >= edev->size)) +		return 0; +	if ((off + count) > edev->size) +		count = edev->size - off; +	if (unlikely(!count)) +		return count; -	cmd_addr = OP_READ << edev->addrlen; +	mutex_lock(&edev->lock); -	if (edev->addrlen == 7) { -		cmd_addr |= off & 0x7f; -		bits = 10; -	} else { -		cmd_addr |= off & 0x3f; -		bits = 9; -	} +	if (edev->pdata->prepare) +		edev->pdata->prepare(edev); -	dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", -		cmd_addr, edev->spi->max_speed_hz); +	while (count) { +		struct spi_message m; +		struct spi_transfer t[2] = { { 0 } }; +		u16 cmd_addr = OP_READ << edev->addrlen; +		size_t nbytes = count; +		int bits; +		int err; + +		if (edev->addrlen == 7) { +			cmd_addr |= off & 0x7f; +			bits = 10; +			if (has_quirk_single_word_read(edev)) +				nbytes = 1; +		} else { +			cmd_addr |= (off >> 1) & 0x3f; +			bits = 9; +			if (has_quirk_single_word_read(edev)) +				nbytes = 2; +		} -	spi_message_init(&m); -	memset(t, 0, sizeof(t)); +		dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", +			cmd_addr, edev->spi->max_speed_hz); -	t[0].tx_buf = (char *)&cmd_addr; -	t[0].len = 2; -	t[0].bits_per_word = bits; -	spi_message_add_tail(&t[0], &m); +		spi_message_init(&m); -	t[1].rx_buf = buf; -	t[1].len = count; -	t[1].bits_per_word = 8; -	spi_message_add_tail(&t[1], &m); +		t[0].tx_buf = (char *)&cmd_addr; +		t[0].len = 2; +		t[0].bits_per_word = bits; +		spi_message_add_tail(&t[0], &m); -	mutex_lock(&edev->lock); +		t[1].rx_buf = buf; +		t[1].len = count; +		t[1].bits_per_word = 8; +		spi_message_add_tail(&t[1], &m); -	if (edev->pdata->prepare) -		edev->pdata->prepare(edev); +		err = spi_sync(edev->spi, &m); +		/* have to wait at least Tcsl ns */ +		ndelay(250); -	ret = spi_sync(edev->spi, &m); -	/* have to wait at least Tcsl ns */ -	ndelay(250); -	if (ret) { -		dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", -			count, (int)off, ret); +		if (err) { +			dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", +				nbytes, (int)off, err); +			ret = err; +			break; +		} + +		buf += nbytes; +		off += nbytes; +		count -= nbytes; +		ret += nbytes;  	}  	if (edev->pdata->finish)  		edev->pdata->finish(edev);  	mutex_unlock(&edev->lock); -	return ret ? : count; +	return ret;  }  static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) @@ -110,7 +153,13 @@ static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on)  		bits = 9;  	} -	dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr); +	if (has_quirk_instruction_length(edev)) { +		cmd_addr <<= 2; +		bits += 2; +	} + +	dev_dbg(&edev->spi->dev, "ew%s cmd 0x%04x, %d bits\n", +			is_on ? "en" : "ds", cmd_addr, bits);  	spi_message_init(&m);  	memset(&t, 0, sizeof(t)); @@ -155,7 +204,7 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,  		bits = 10;  		data_len = 1;  	} else { -		cmd_addr |= off & 0x3f; +		cmd_addr |= (off >> 1) & 0x3f;  		bits = 9;  		data_len = 2;  	} @@ -182,16 +231,17 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,  }  static ssize_t -eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, -			struct bin_attribute *bin_attr, -			char *buf, loff_t off, size_t count) +eeprom_93xx46_write(struct eeprom_93xx46_dev *edev, const char *buf, +		    loff_t off, size_t count)  { -	struct eeprom_93xx46_dev *edev; -	struct device *dev;  	int i, ret, step = 1; -	dev = container_of(kobj, struct device, kobj); -	edev = dev_get_drvdata(dev); +	if (unlikely(off >= edev->size)) +		return -EFBIG; +	if ((off + count) > edev->size) +		count = edev->size - off; +	if (unlikely(!count)) +		return count;  	/* only write even number of bytes on 16-bit devices */  	if (edev->addrlen == 6) { @@ -228,6 +278,49 @@ eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj,  	return ret ? : count;  } +/* + * Provide a regmap interface, which is registered with the NVMEM + * framework +*/ +static int eeprom_93xx46_regmap_read(void *context, const void *reg, +				     size_t reg_size, void *val, +				     size_t val_size) +{ +	struct eeprom_93xx46_dev *eeprom_93xx46 = context; +	off_t offset = *(u32 *)reg; +	int err; + +	err = eeprom_93xx46_read(eeprom_93xx46, val, offset, val_size); +	if (err) +		return err; +	return 0; +} + +static int eeprom_93xx46_regmap_write(void *context, const void *data, +				      size_t count) +{ +	struct eeprom_93xx46_dev *eeprom_93xx46 = context; +	const char *buf; +	u32 offset; +	size_t len; +	int err; + +	memcpy(&offset, data, sizeof(offset)); +	buf = (const char *)data + sizeof(offset); +	len = count - sizeof(offset); + +	err = eeprom_93xx46_write(eeprom_93xx46, buf, offset, len); +	if (err) +		return err; +	return 0; +} + +static const struct regmap_bus eeprom_93xx46_regmap_bus = { +	.read = eeprom_93xx46_regmap_read, +	.write = eeprom_93xx46_regmap_write, +	.reg_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; +  static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev)  {  	struct eeprom_93xx46_platform_data *pd = edev->pdata; @@ -245,6 +338,13 @@ static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev)  		bits = 9;  	} +	if (has_quirk_instruction_length(edev)) { +		cmd_addr <<= 2; +		bits += 2; +	} + +	dev_dbg(&edev->spi->dev, "eral cmd 0x%04x, %d bits\n", cmd_addr, bits); +  	spi_message_init(&m);  	memset(&t, 0, sizeof(t)); @@ -294,12 +394,101 @@ static ssize_t eeprom_93xx46_store_erase(struct device *dev,  }  static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase); +static void select_assert(void *context) +{ +	struct eeprom_93xx46_dev *edev = context; + +	gpiod_set_value_cansleep(edev->pdata->select, 1); +} + +static void select_deassert(void *context) +{ +	struct eeprom_93xx46_dev *edev = context; + +	gpiod_set_value_cansleep(edev->pdata->select, 0); +} + +static const struct of_device_id eeprom_93xx46_of_table[] = { +	{ .compatible = "eeprom-93xx46", }, +	{ .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, }, +	{} +}; +MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table); + +static int eeprom_93xx46_probe_dt(struct spi_device *spi) +{ +	const struct of_device_id *of_id = +		of_match_device(eeprom_93xx46_of_table, &spi->dev); +	struct device_node *np = spi->dev.of_node; +	struct eeprom_93xx46_platform_data *pd; +	u32 tmp; +	int gpio; +	enum of_gpio_flags of_flags; +	int ret; + +	pd = devm_kzalloc(&spi->dev, sizeof(*pd), GFP_KERNEL); +	if (!pd) +		return -ENOMEM; + +	ret = of_property_read_u32(np, "data-size", &tmp); +	if (ret < 0) { +		dev_err(&spi->dev, "data-size property not found\n"); +		return ret; +	} + +	if (tmp == 8) { +		pd->flags |= EE_ADDR8; +	} else if (tmp == 16) { +		pd->flags |= EE_ADDR16; +	} else { +		dev_err(&spi->dev, "invalid data-size (%d)\n", tmp); +		return -EINVAL; +	} + +	if (of_property_read_bool(np, "read-only")) +		pd->flags |= EE_READONLY; + +	gpio = of_get_named_gpio_flags(np, "select-gpios", 0, &of_flags); +	if (gpio_is_valid(gpio)) { +		unsigned long flags = +			of_flags == OF_GPIO_ACTIVE_LOW ? GPIOF_ACTIVE_LOW : 0; + +		ret = devm_gpio_request_one(&spi->dev, gpio, flags, +					    "eeprom_93xx46_select"); +		if (ret) +			return ret; + +		pd->select = gpio_to_desc(gpio); +		pd->prepare = select_assert; +		pd->finish = select_deassert; + +		gpiod_direction_output(pd->select, 0); +	} + +	if (of_id->data) { +		const struct eeprom_93xx46_devtype_data *data = of_id->data; + +		pd->quirks = data->quirks; +	} + +	spi->dev.platform_data = pd; + +	return 0; +} +  static int eeprom_93xx46_probe(struct spi_device *spi)  {  	struct eeprom_93xx46_platform_data *pd;  	struct eeprom_93xx46_dev *edev; +	struct regmap *regmap;  	int err; +	if (spi->dev.of_node) { +		err = eeprom_93xx46_probe_dt(spi); +		if (err < 0) +			return err; +	} +  	pd = spi->dev.platform_data;  	if (!pd) {  		dev_err(&spi->dev, "missing platform data\n"); @@ -325,19 +514,34 @@ static int eeprom_93xx46_probe(struct spi_device *spi)  	edev->spi = spi_dev_get(spi);  	edev->pdata = pd; -	sysfs_bin_attr_init(&edev->bin); -	edev->bin.attr.name = "eeprom"; -	edev->bin.attr.mode = S_IRUSR; -	edev->bin.read = eeprom_93xx46_bin_read; -	edev->bin.size = 128; -	if (!(pd->flags & EE_READONLY)) { -		edev->bin.write = eeprom_93xx46_bin_write; -		edev->bin.attr.mode |= S_IWUSR; +	edev->size = 128; + +	edev->regmap_config.reg_bits = 32; +	edev->regmap_config.val_bits = 8; +	edev->regmap_config.reg_stride = 1; +	edev->regmap_config.max_register = edev->size - 1; + +	regmap = devm_regmap_init(&spi->dev, &eeprom_93xx46_regmap_bus, edev, +				  &edev->regmap_config); +	if (IS_ERR(regmap)) { +		dev_err(&spi->dev, "regmap init failed\n"); +		err = PTR_ERR(regmap); +		goto fail;  	} -	err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin); -	if (err) +	edev->nvmem_config.name = dev_name(&spi->dev); +	edev->nvmem_config.dev = &spi->dev; +	edev->nvmem_config.read_only = pd->flags & EE_READONLY; +	edev->nvmem_config.root_only = true; +	edev->nvmem_config.owner = THIS_MODULE; +	edev->nvmem_config.compat = true; +	edev->nvmem_config.base_dev = &spi->dev; + +	edev->nvmem = nvmem_register(&edev->nvmem_config); +	if (IS_ERR(edev->nvmem)) { +		err = PTR_ERR(edev->nvmem);  		goto fail; +	}  	dev_info(&spi->dev, "%d-bit eeprom %s\n",  		(pd->flags & EE_ADDR8) ? 8 : 16, @@ -359,10 +563,11 @@ static int eeprom_93xx46_remove(struct spi_device *spi)  {  	struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi); +	nvmem_unregister(edev->nvmem); +  	if (!(edev->pdata->flags & EE_READONLY))  		device_remove_file(&spi->dev, &dev_attr_erase); -	sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin);  	kfree(edev);  	return 0;  } @@ -370,6 +575,7 @@ static int eeprom_93xx46_remove(struct spi_device *spi)  static struct spi_driver eeprom_93xx46_driver = {  	.driver = {  		.name	= "93xx46", +		.of_match_table = of_match_ptr(eeprom_93xx46_of_table),  	},  	.probe		= eeprom_93xx46_probe,  	.remove		= eeprom_93xx46_remove, diff --git a/drivers/misc/genwqe/card_sysfs.c b/drivers/misc/genwqe/card_sysfs.c index 6ab31eff0536..c24c9b7c1dd3 100644 --- a/drivers/misc/genwqe/card_sysfs.c +++ b/drivers/misc/genwqe/card_sysfs.c @@ -278,7 +278,7 @@ static umode_t genwqe_is_visible(struct kobject *kobj,  				 struct attribute *attr, int n)  {  	unsigned int j; -	struct device *dev = container_of(kobj, struct device, kobj); +	struct device *dev = kobj_to_dev(kobj);  	struct genwqe_dev *cd = dev_get_drvdata(dev);  	umode_t mode = attr->mode; diff --git a/drivers/misc/ibmasm/ibmasm.h b/drivers/misc/ibmasm/ibmasm.h index 5bd127727d8e..9fea49d2e15b 100644 --- a/drivers/misc/ibmasm/ibmasm.h +++ b/drivers/misc/ibmasm/ibmasm.h @@ -34,6 +34,7 @@  #include <linux/kref.h>  #include <linux/device.h>  #include <linux/input.h> +#include <linux/time64.h>  /* Driver identification */  #define DRIVER_NAME	"ibmasm" @@ -53,9 +54,11 @@ extern int ibmasm_debug;  static inline char *get_timestamp(char *buf)  { -	struct timeval now; -	do_gettimeofday(&now); -	sprintf(buf, "%lu.%lu", now.tv_sec, now.tv_usec); +	struct timespec64 now; + +	ktime_get_real_ts64(&now); +	sprintf(buf, "%llu.%.08lu", (long long)now.tv_sec, +				now.tv_nsec / NSEC_PER_USEC);  	return buf;  } diff --git a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c index 0c3bb7e3ee80..14b7d539fed6 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -209,7 +209,7 @@ static int lis3lv02d_i2c_remove(struct i2c_client *client)  #ifdef CONFIG_PM_SLEEP  static int lis3lv02d_i2c_suspend(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct lis3lv02d *lis3 = i2c_get_clientdata(client);  	if (!lis3->pdata || !lis3->pdata->wakeup_flags) @@ -219,7 +219,7 @@ static int lis3lv02d_i2c_suspend(struct device *dev)  static int lis3lv02d_i2c_resume(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct lis3lv02d *lis3 = i2c_get_clientdata(client);  	/* @@ -238,7 +238,7 @@ static int lis3lv02d_i2c_resume(struct device *dev)  #ifdef CONFIG_PM  static int lis3_i2c_runtime_suspend(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct lis3lv02d *lis3 = i2c_get_clientdata(client);  	lis3lv02d_poweroff(lis3); @@ -247,7 +247,7 @@ static int lis3_i2c_runtime_suspend(struct device *dev)  static int lis3_i2c_runtime_resume(struct device *dev)  { -	struct i2c_client *client = container_of(dev, struct i2c_client, dev); +	struct i2c_client *client = to_i2c_client(dev);  	struct lis3lv02d *lis3 = i2c_get_clientdata(client);  	lis3lv02d_poweron(lis3); diff --git a/drivers/misc/lkdtm.c b/drivers/misc/lkdtm.c index 11fdadc68e53..5f1a36b8fbb0 100644 --- a/drivers/misc/lkdtm.c +++ b/drivers/misc/lkdtm.c @@ -92,6 +92,9 @@ enum ctype {  	CT_UNALIGNED_LOAD_STORE_WRITE,  	CT_OVERWRITE_ALLOCATION,  	CT_WRITE_AFTER_FREE, +	CT_READ_AFTER_FREE, +	CT_WRITE_BUDDY_AFTER_FREE, +	CT_READ_BUDDY_AFTER_FREE,  	CT_SOFTLOCKUP,  	CT_HARDLOCKUP,  	CT_SPINLOCKUP, @@ -103,7 +106,9 @@ enum ctype {  	CT_EXEC_USERSPACE,  	CT_ACCESS_USERSPACE,  	CT_WRITE_RO, +	CT_WRITE_RO_AFTER_INIT,  	CT_WRITE_KERN, +	CT_WRAP_ATOMIC  };  static char* cp_name[] = { @@ -129,6 +134,9 @@ static char* cp_type[] = {  	"UNALIGNED_LOAD_STORE_WRITE",  	"OVERWRITE_ALLOCATION",  	"WRITE_AFTER_FREE", +	"READ_AFTER_FREE", +	"WRITE_BUDDY_AFTER_FREE", +	"READ_BUDDY_AFTER_FREE",  	"SOFTLOCKUP",  	"HARDLOCKUP",  	"SPINLOCKUP", @@ -140,7 +148,9 @@ static char* cp_type[] = {  	"EXEC_USERSPACE",  	"ACCESS_USERSPACE",  	"WRITE_RO", +	"WRITE_RO_AFTER_INIT",  	"WRITE_KERN", +	"WRAP_ATOMIC"  };  static struct jprobe lkdtm; @@ -162,6 +172,7 @@ static DEFINE_SPINLOCK(lock_me_up);  static u8 data_area[EXEC_SIZE];  static const unsigned long rodata = 0xAA55AA55; +static unsigned long ro_after_init __ro_after_init = 0x55AA5500;  module_param(recur_count, int, 0644);  MODULE_PARM_DESC(recur_count, " Recursion level for the stack overflow test"); @@ -335,7 +346,7 @@ static noinline void corrupt_stack(void)  	memset((void *)data, 0, 64);  } -static void execute_location(void *dst) +static void noinline execute_location(void *dst)  {  	void (*func)(void) = dst; @@ -409,12 +420,109 @@ static void lkdtm_do_action(enum ctype which)  		break;  	}  	case CT_WRITE_AFTER_FREE: { +		int *base, *again;  		size_t len = 1024; -		u32 *data = kmalloc(len, GFP_KERNEL); +		/* +		 * The slub allocator uses the first word to store the free +		 * pointer in some configurations. Use the middle of the +		 * allocation to avoid running into the freelist +		 */ +		size_t offset = (len / sizeof(*base)) / 2; + +		base = kmalloc(len, GFP_KERNEL); +		pr_info("Allocated memory %p-%p\n", base, &base[offset * 2]); +		pr_info("Attempting bad write to freed memory at %p\n", +			&base[offset]); +		kfree(base); +		base[offset] = 0x0abcdef0; +		/* Attempt to notice the overwrite. */ +		again = kmalloc(len, GFP_KERNEL); +		kfree(again); +		if (again != base) +			pr_info("Hmm, didn't get the same memory range.\n"); -		kfree(data); +		break; +	} +	case CT_READ_AFTER_FREE: { +		int *base, *val, saw; +		size_t len = 1024; +		/* +		 * The slub allocator uses the first word to store the free +		 * pointer in some configurations. Use the middle of the +		 * allocation to avoid running into the freelist +		 */ +		size_t offset = (len / sizeof(*base)) / 2; + +		base = kmalloc(len, GFP_KERNEL); +		if (!base) +			break; + +		val = kmalloc(len, GFP_KERNEL); +		if (!val) +			break; + +		*val = 0x12345678; +		base[offset] = *val; +		pr_info("Value in memory before free: %x\n", base[offset]); + +		kfree(base); + +		pr_info("Attempting bad read from freed memory\n"); +		saw = base[offset]; +		if (saw != *val) { +			/* Good! Poisoning happened, so declare a win. */ +			pr_info("Memory correctly poisoned (%x)\n", saw); +			BUG(); +		} +		pr_info("Memory was not poisoned\n"); + +		kfree(val); +		break; +	} +	case CT_WRITE_BUDDY_AFTER_FREE: { +		unsigned long p = __get_free_page(GFP_KERNEL); +		if (!p) +			break; +		pr_info("Writing to the buddy page before free\n"); +		memset((void *)p, 0x3, PAGE_SIZE); +		free_page(p);  		schedule(); -		memset(data, 0x78, len); +		pr_info("Attempting bad write to the buddy page after free\n"); +		memset((void *)p, 0x78, PAGE_SIZE); +		/* Attempt to notice the overwrite. */ +		p = __get_free_page(GFP_KERNEL); +		free_page(p); +		schedule(); + +		break; +	} +	case CT_READ_BUDDY_AFTER_FREE: { +		unsigned long p = __get_free_page(GFP_KERNEL); +		int saw, *val = kmalloc(1024, GFP_KERNEL); +		int *base; + +		if (!p) +			break; + +		if (!val) +			break; + +		base = (int *)p; + +		*val = 0x12345678; +		base[0] = *val; +		pr_info("Value in memory before free: %x\n", base[0]); +		free_page(p); +		pr_info("Attempting to read from freed memory\n"); +		saw = base[0]; +		if (saw != *val) { +			/* Good! Poisoning happened, so declare a win. */ +			pr_info("Memory correctly poisoned (%x)\n", saw); +			BUG(); +		} +		pr_info("Buddy page was not poisoned\n"); + +		kfree(val);  		break;  	}  	case CT_SOFTLOCKUP: @@ -503,11 +611,28 @@ static void lkdtm_do_action(enum ctype which)  		break;  	}  	case CT_WRITE_RO: { -		unsigned long *ptr; +		/* Explicitly cast away "const" for the test. */ +		unsigned long *ptr = (unsigned long *)&rodata; -		ptr = (unsigned long *)&rodata; +		pr_info("attempting bad rodata write at %p\n", ptr); +		*ptr ^= 0xabcd1234; -		pr_info("attempting bad write at %p\n", ptr); +		break; +	} +	case CT_WRITE_RO_AFTER_INIT: { +		unsigned long *ptr = &ro_after_init; + +		/* +		 * Verify we were written to during init. Since an Oops +		 * is considered a "success", a failure is to just skip the +		 * real test. +		 */ +		if ((*ptr & 0xAA) != 0xAA) { +			pr_info("%p was NOT written during init!?\n", ptr); +			break; +		} + +		pr_info("attempting bad ro_after_init write at %p\n", ptr);  		*ptr ^= 0xabcd1234;  		break; @@ -528,6 +653,17 @@ static void lkdtm_do_action(enum ctype which)  		do_overwritten();  		break;  	} +	case CT_WRAP_ATOMIC: { +		atomic_t under = ATOMIC_INIT(INT_MIN); +		atomic_t over = ATOMIC_INIT(INT_MAX); + +		pr_info("attempting atomic underflow\n"); +		atomic_dec(&under); +		pr_info("attempting atomic overflow\n"); +		atomic_inc(&over); + +		return; +	}  	case CT_NONE:  	default:  		break; @@ -817,6 +953,9 @@ static int __init lkdtm_module_init(void)  	int n_debugfs_entries = 1; /* Assume only the direct entry */  	int i; +	/* Make sure we can write to __ro_after_init values during __init */ +	ro_after_init |= 0xAA; +  	/* Register debugfs interface */  	lkdtm_debugfs_root = debugfs_create_dir("provoke-crash", NULL);  	if (!lkdtm_debugfs_root) { diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index d23384dde73b..c49e1d2269af 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -1,6 +1,6 @@  config INTEL_MEI  	tristate "Intel Management Engine Interface" -	depends on X86 && PCI && WATCHDOG_CORE +	depends on X86 && PCI  	help  	  The Intel Management Engine (Intel ME) provides Manageability,  	  Security and Media services for system containing Intel chipsets. @@ -12,7 +12,7 @@ config INTEL_MEI  config INTEL_MEI_ME  	tristate "ME Enabled Intel Chipsets"  	select INTEL_MEI -	depends on X86 && PCI && WATCHDOG_CORE +	depends on X86 && PCI  	help  	  MEI support for ME Enabled Intel chipsets. @@ -37,7 +37,7 @@ config INTEL_MEI_ME  config INTEL_MEI_TXE  	tristate "Intel Trusted Execution Environment with ME Interface"  	select INTEL_MEI -	depends on X86 && PCI && WATCHDOG_CORE +	depends on X86 && PCI  	help  	  MEI Support for Trusted Execution Environment device on Intel SoCs diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 01447ca21c26..59e6b0aede34 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -9,7 +9,6 @@ mei-objs += interrupt.o  mei-objs += client.o  mei-objs += main.o  mei-objs += amthif.o -mei-objs += wd.o  mei-objs += bus.o  mei-objs += bus-fixup.o  mei-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index cd0403f09267..194360a5f782 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -50,7 +50,6 @@ void mei_amthif_reset_params(struct mei_device *dev)  	dev->iamthif_current_cb = NULL;  	dev->iamthif_canceled = false;  	dev->iamthif_state = MEI_IAMTHIF_IDLE; -	dev->iamthif_timer = 0;  	dev->iamthif_stall_timer = 0;  	dev->iamthif_open_count = 0;  } @@ -68,11 +67,14 @@ int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)  	struct mei_cl *cl = &dev->iamthif_cl;  	int ret; +	if (mei_cl_is_connected(cl)) +		return 0; +  	dev->iamthif_state = MEI_IAMTHIF_IDLE;  	mei_cl_init(cl, dev); -	ret = mei_cl_link(cl, MEI_IAMTHIF_HOST_CLIENT_ID); +	ret = mei_cl_link(cl);  	if (ret < 0) {  		dev_err(dev->dev, "amthif: failed cl_link %d\n", ret);  		return ret; @@ -80,32 +82,10 @@ int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)  	ret = mei_cl_connect(cl, me_cl, NULL); -	dev->iamthif_state = MEI_IAMTHIF_IDLE; -  	return ret;  }  /** - * mei_amthif_find_read_list_entry - finds a amthilist entry for current file - * - * @dev: the device structure - * @file: pointer to file object - * - * Return:   returned a list entry on success, NULL on failure. - */ -struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, -						struct file *file) -{ -	struct mei_cl_cb *cb; - -	list_for_each_entry(cb, &dev->amthif_rd_complete_list.list, list) -		if (cb->file_object == file) -			return cb; -	return NULL; -} - - -/**   * mei_amthif_read - read data from AMTHIF client   *   * @dev: the device structure @@ -126,18 +106,11 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,  {  	struct mei_cl *cl = file->private_data;  	struct mei_cl_cb *cb; -	unsigned long timeout;  	int rets;  	int wait_ret; -	/* Only possible if we are in timeout */ -	if (!cl) { -		dev_err(dev->dev, "bad file ext.\n"); -		return -ETIME; -	} -  	dev_dbg(dev->dev, "checking amthif data\n"); -	cb = mei_amthif_find_read_list_entry(dev, file); +	cb = mei_cl_read_cb(cl, file);  	/* Check for if we can block or not*/  	if (cb == NULL && file->f_flags & O_NONBLOCK) @@ -149,8 +122,9 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,  		/* unlock the Mutex */  		mutex_unlock(&dev->device_lock); -		wait_ret = wait_event_interruptible(dev->iamthif_cl.wait, -			(cb = mei_amthif_find_read_list_entry(dev, file))); +		wait_ret = wait_event_interruptible(cl->rx_wait, +					!list_empty(&cl->rd_completed) || +					!mei_cl_is_connected(cl));  		/* Locking again the Mutex */  		mutex_lock(&dev->device_lock); @@ -158,7 +132,12 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,  		if (wait_ret)  			return -ERESTARTSYS; -		dev_dbg(dev->dev, "woke up from sleep\n"); +		if (!mei_cl_is_connected(cl)) { +			rets = -EBUSY; +			goto out; +		} + +		cb = mei_cl_read_cb(cl, file);  	}  	if (cb->status) { @@ -168,24 +147,10 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,  	}  	dev_dbg(dev->dev, "Got amthif data\n"); -	dev->iamthif_timer = 0; - -	timeout = cb->read_time + -		mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); -	dev_dbg(dev->dev, "amthif timeout = %lud\n", -			timeout); - -	if  (time_after(jiffies, timeout)) { -		dev_dbg(dev->dev, "amthif Time out\n"); -		/* 15 sec for the message has expired */ -		list_del_init(&cb->list); -		rets = -ETIME; -		goto free; -	}  	/* if the whole message will fit remove it from the list */  	if (cb->buf_idx >= *offset && length >= (cb->buf_idx - *offset))  		list_del_init(&cb->list); -	else if (cb->buf_idx > 0 && cb->buf_idx <= *offset) { +	else if (cb->buf_idx <= *offset) {  		/* end of the message has been reached */  		list_del_init(&cb->list);  		rets = 0; @@ -195,9 +160,8 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,  		 * remove message from deletion list  		 */ -	dev_dbg(dev->dev, "amthif cb->buf size - %d\n", -	    cb->buf.size); -	dev_dbg(dev->dev, "amthif cb->buf_idx - %lu\n", cb->buf_idx); +	dev_dbg(dev->dev, "amthif cb->buf.size - %zu cb->buf_idx - %zu\n", +		cb->buf.size, cb->buf_idx);  	/* length is being truncated to PAGE_SIZE, however,  	 * the buf_idx may point beyond */ @@ -229,7 +193,7 @@ out:   *   * Return: 0 on success, <0 on failure.   */ -static int mei_amthif_read_start(struct mei_cl *cl, struct file *file) +static int mei_amthif_read_start(struct mei_cl *cl, const struct file *file)  {  	struct mei_device *dev = cl->dev;  	struct mei_cl_cb *cb; @@ -248,7 +212,7 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file)  	list_add_tail(&cb->list, &dev->ctrl_wr_list.list);  	dev->iamthif_state = MEI_IAMTHIF_READING; -	dev->iamthif_file_object = cb->file_object; +	dev->iamthif_fp = cb->fp;  	dev->iamthif_current_cb = cb;  	return 0; @@ -277,7 +241,7 @@ static int mei_amthif_send_cmd(struct mei_cl *cl, struct mei_cl_cb *cb)  	dev->iamthif_state = MEI_IAMTHIF_WRITING;  	dev->iamthif_current_cb = cb; -	dev->iamthif_file_object = cb->file_object; +	dev->iamthif_fp = cb->fp;  	dev->iamthif_canceled = false;  	ret = mei_cl_write(cl, cb, false); @@ -285,7 +249,7 @@ static int mei_amthif_send_cmd(struct mei_cl *cl, struct mei_cl_cb *cb)  		return ret;  	if (cb->completed) -		cb->status = mei_amthif_read_start(cl, cb->file_object); +		cb->status = mei_amthif_read_start(cl, cb->fp);  	return 0;  } @@ -304,8 +268,7 @@ int mei_amthif_run_next_cmd(struct mei_device *dev)  	dev->iamthif_canceled = false;  	dev->iamthif_state = MEI_IAMTHIF_IDLE; -	dev->iamthif_timer = 0; -	dev->iamthif_file_object = NULL; +	dev->iamthif_fp = NULL;  	dev_dbg(dev->dev, "complete amthif cmd_list cb.\n"); @@ -329,17 +292,17 @@ int mei_amthif_run_next_cmd(struct mei_device *dev)  int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb)  { -	struct mei_device *dev; - -	if (WARN_ON(!cl || !cl->dev)) -		return -ENODEV; +	struct mei_device *dev = cl->dev; -	if (WARN_ON(!cb)) -		return -EINVAL; +	list_add_tail(&cb->list, &dev->amthif_cmd_list.list); -	dev = cl->dev; +	/* +	 * The previous request is still in processing, queue this one. +	 */ +	if (dev->iamthif_state > MEI_IAMTHIF_IDLE && +	    dev->iamthif_state < MEI_IAMTHIF_READ_COMPLETE) +		return 0; -	list_add_tail(&cb->list, &dev->amthif_cmd_list.list);  	return mei_amthif_run_next_cmd(dev);  } @@ -360,10 +323,10 @@ unsigned int mei_amthif_poll(struct mei_device *dev,  {  	unsigned int mask = 0; -	poll_wait(file, &dev->iamthif_cl.wait, wait); +	poll_wait(file, &dev->iamthif_cl.rx_wait, wait);  	if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && -	    dev->iamthif_file_object == file) { +	    dev->iamthif_fp == file) {  		mask |= POLLIN | POLLRDNORM;  		mei_amthif_run_next_cmd(dev); @@ -393,7 +356,7 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,  		return ret;  	if (cb->completed) -		cb->status = mei_amthif_read_start(cl, cb->file_object); +		cb->status = mei_amthif_read_start(cl, cb->fp);  	return 0;  } @@ -437,11 +400,12 @@ int mei_amthif_irq_read_msg(struct mei_cl *cl,  /**   * mei_amthif_complete - complete amthif callback.   * - * @dev: the device structure. + * @cl: host client   * @cb: callback block.   */ -void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb) +void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb)  { +	struct mei_device *dev = cl->dev;  	if (cb->fop_type == MEI_FOP_WRITE) {  		if (!cb->status) { @@ -453,25 +417,22 @@ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb)  		 * in case of error enqueue the write cb to complete read list  		 * so it can be propagated to the reader  		 */ -		list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list); -		wake_up_interruptible(&dev->iamthif_cl.wait); +		list_add_tail(&cb->list, &cl->rd_completed); +		wake_up_interruptible(&cl->rx_wait);  		return;  	}  	if (!dev->iamthif_canceled) {  		dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE;  		dev->iamthif_stall_timer = 0; -		list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list); +		list_add_tail(&cb->list, &cl->rd_completed);  		dev_dbg(dev->dev, "amthif read completed\n"); -		dev->iamthif_timer = jiffies; -		dev_dbg(dev->dev, "dev->iamthif_timer = %ld\n", -			dev->iamthif_timer);  	} else {  		mei_amthif_run_next_cmd(dev);  	}  	dev_dbg(dev->dev, "completing amthif call back.\n"); -	wake_up_interruptible(&dev->iamthif_cl.wait); +	wake_up_interruptible(&cl->rx_wait);  }  /** @@ -497,7 +458,7 @@ static bool mei_clear_list(struct mei_device *dev,  	/* list all list member */  	list_for_each_entry_safe(cb, next, mei_cb_list, list) {  		/* check if list member associated with a file */ -		if (file == cb->file_object) { +		if (file == cb->fp) {  			/* check if cb equal to current iamthif cb */  			if (dev->iamthif_current_cb == cb) {  				dev->iamthif_current_cb = NULL; @@ -523,13 +484,14 @@ static bool mei_clear_list(struct mei_device *dev,   *   * Return: true if callback removed from the list, false otherwise   */ -static bool mei_clear_lists(struct mei_device *dev, struct file *file) +static bool mei_clear_lists(struct mei_device *dev, const struct file *file)  {  	bool removed = false; +	struct mei_cl *cl = &dev->iamthif_cl;  	/* remove callbacks associated with a file */  	mei_clear_list(dev, file, &dev->amthif_cmd_list.list); -	if (mei_clear_list(dev, file, &dev->amthif_rd_complete_list.list)) +	if (mei_clear_list(dev, file, &cl->rd_completed))  		removed = true;  	mei_clear_list(dev, file, &dev->ctrl_rd_list.list); @@ -546,7 +508,7 @@ static bool mei_clear_lists(struct mei_device *dev, struct file *file)  	/* check if iamthif_current_cb not NULL */  	if (dev->iamthif_current_cb && !removed) {  		/* check file and iamthif current cb association */ -		if (dev->iamthif_current_cb->file_object == file) { +		if (dev->iamthif_current_cb->fp == file) {  			/* remove cb */  			mei_io_cb_free(dev->iamthif_current_cb);  			dev->iamthif_current_cb = NULL; @@ -569,7 +531,7 @@ int mei_amthif_release(struct mei_device *dev, struct file *file)  	if (dev->iamthif_open_count > 0)  		dev->iamthif_open_count--; -	if (dev->iamthif_file_object == file && +	if (dev->iamthif_fp == file &&  	    dev->iamthif_state != MEI_IAMTHIF_IDLE) {  		dev_dbg(dev->dev, "amthif canceled iamthif state %d\n", diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 020de5919c21..e9e6ea3ab73c 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -35,6 +35,9 @@ static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO;  #define MEI_UUID_NFC_HCI UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, \  			0x94, 0xd4, 0x50, 0x26, 0x67, 0x23, 0x77, 0x5c) +#define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \ +			    0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB) +  #define MEI_UUID_ANY NULL_UUID_LE  /** @@ -48,8 +51,7 @@ static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO;   */  static void number_of_connections(struct mei_cl_device *cldev)  { -	dev_dbg(&cldev->dev, "running hook %s on %pUl\n", -			__func__, mei_me_cl_uuid(cldev->me_cl)); +	dev_dbg(&cldev->dev, "running hook %s\n", __func__);  	if (cldev->me_cl->props.max_number_of_connections > 1)  		cldev->do_match = 0; @@ -62,11 +64,36 @@ static void number_of_connections(struct mei_cl_device *cldev)   */  static void blacklist(struct mei_cl_device *cldev)  { -	dev_dbg(&cldev->dev, "running hook %s on %pUl\n", -			__func__, mei_me_cl_uuid(cldev->me_cl)); +	dev_dbg(&cldev->dev, "running hook %s\n", __func__); +  	cldev->do_match = 0;  } +/** + * mei_wd - wd client on the bus, change protocol version + *   as the API has changed. + * + * @cldev: me clients device + */ +#if IS_ENABLED(CONFIG_INTEL_MEI_ME) +#include <linux/pci.h> +#include "hw-me-regs.h" +static void mei_wd(struct mei_cl_device *cldev) +{ +	struct pci_dev *pdev = to_pci_dev(cldev->dev.parent); + +	dev_dbg(&cldev->dev, "running hook %s\n", __func__); +	if (pdev->device == MEI_DEV_ID_WPT_LP || +	    pdev->device == MEI_DEV_ID_SPT || +	    pdev->device == MEI_DEV_ID_SPT_H) +		cldev->me_cl->props.protocol_version = 0x2; + +	cldev->do_match = 1; +} +#else +static inline void mei_wd(struct mei_cl_device *cldev) {} +#endif /* CONFIG_INTEL_MEI_ME */ +  struct mei_nfc_cmd {  	u8 command;  	u8 status; @@ -208,12 +235,11 @@ static void mei_nfc(struct mei_cl_device *cldev)  	bus = cldev->bus; -	dev_dbg(bus->dev, "running hook %s: %pUl match=%d\n", -		__func__, mei_me_cl_uuid(cldev->me_cl), cldev->do_match); +	dev_dbg(&cldev->dev, "running hook %s\n", __func__);  	mutex_lock(&bus->device_lock);  	/* we need to connect to INFO GUID */ -	cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY); +	cl = mei_cl_alloc_linked(bus);  	if (IS_ERR(cl)) {  		ret = PTR_ERR(cl);  		cl = NULL; @@ -282,6 +308,7 @@ static struct mei_fixup {  	MEI_FIXUP(MEI_UUID_ANY, number_of_connections),  	MEI_FIXUP(MEI_UUID_NFC_INFO, blacklist),  	MEI_FIXUP(MEI_UUID_NFC_HCI, mei_nfc), +	MEI_FIXUP(MEI_UUID_WD, mei_wd),  };  /** diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 0b05aa938799..5d5996e39a67 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -44,7 +44,7 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,  			bool blocking)  {  	struct mei_device *bus; -	struct mei_cl_cb *cb = NULL; +	struct mei_cl_cb *cb;  	ssize_t rets;  	if (WARN_ON(!cl || !cl->dev)) @@ -53,6 +53,11 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,  	bus = cl->dev;  	mutex_lock(&bus->device_lock); +	if (bus->dev_state != MEI_DEV_ENABLED) { +		rets = -ENODEV; +		goto out; +	} +  	if (!mei_cl_is_connected(cl)) {  		rets = -ENODEV;  		goto out; @@ -81,8 +86,6 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,  out:  	mutex_unlock(&bus->device_lock); -	if (rets < 0) -		mei_io_cb_free(cb);  	return rets;  } @@ -109,6 +112,10 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length)  	bus = cl->dev;  	mutex_lock(&bus->device_lock); +	if (bus->dev_state != MEI_DEV_ENABLED) { +		rets = -ENODEV; +		goto out; +	}  	cb = mei_cl_read_cb(cl, NULL);  	if (cb) @@ -230,45 +237,55 @@ static void mei_cl_bus_event_work(struct work_struct *work)   * mei_cl_bus_notify_event - schedule notify cb on bus client   *   * @cl: host client + * + * Return: true if event was scheduled + *         false if the client is not waiting for event   */ -void mei_cl_bus_notify_event(struct mei_cl *cl) +bool mei_cl_bus_notify_event(struct mei_cl *cl)  {  	struct mei_cl_device *cldev = cl->cldev;  	if (!cldev || !cldev->event_cb) -		return; +		return false;  	if (!(cldev->events_mask & BIT(MEI_CL_EVENT_NOTIF))) -		return; +		return false;  	if (!cl->notify_ev) -		return; +		return false;  	set_bit(MEI_CL_EVENT_NOTIF, &cldev->events);  	schedule_work(&cldev->event_work);  	cl->notify_ev = false; + +	return true;  }  /** - * mei_cl_bus_rx_event  - schedule rx evenet + * mei_cl_bus_rx_event  - schedule rx event   *   * @cl: host client + * + * Return: true if event was scheduled + *         false if the client is not waiting for event   */ -void mei_cl_bus_rx_event(struct mei_cl *cl) +bool mei_cl_bus_rx_event(struct mei_cl *cl)  {  	struct mei_cl_device *cldev = cl->cldev;  	if (!cldev || !cldev->event_cb) -		return; +		return false;  	if (!(cldev->events_mask & BIT(MEI_CL_EVENT_RX))) -		return; +		return false;  	set_bit(MEI_CL_EVENT_RX, &cldev->events);  	schedule_work(&cldev->event_work); + +	return true;  }  /** @@ -398,7 +415,7 @@ int mei_cldev_enable(struct mei_cl_device *cldev)  	if (!cl) {  		mutex_lock(&bus->device_lock); -		cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY); +		cl = mei_cl_alloc_linked(bus);  		mutex_unlock(&bus->device_lock);  		if (IS_ERR(cl))  			return PTR_ERR(cl); @@ -958,6 +975,22 @@ void mei_cl_bus_rescan(struct mei_device *bus)  	dev_dbg(bus->dev, "rescan end");  } +void mei_cl_bus_rescan_work(struct work_struct *work) +{ +	struct mei_device *bus = +		container_of(work, struct mei_device, bus_rescan_work); +	struct mei_me_client *me_cl; + +	mutex_lock(&bus->device_lock); +	me_cl = mei_me_cl_by_uuid(bus, &mei_amthif_guid); +	if (me_cl) +		mei_amthif_host_init(bus, me_cl); +	mei_me_cl_put(me_cl); +	mutex_unlock(&bus->device_lock); + +	mei_cl_bus_rescan(bus); +} +  int __mei_cldev_driver_register(struct mei_cl_driver *cldrv,  				struct module *owner)  { diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index a6c87c713193..bab17e4197b6 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -359,7 +359,7 @@ void mei_io_cb_free(struct mei_cl_cb *cb)   * Return: mei_cl_cb pointer or NULL;   */  struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type, -				 struct file *fp) +				 const struct file *fp)  {  	struct mei_cl_cb *cb; @@ -368,7 +368,7 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,  		return NULL;  	INIT_LIST_HEAD(&cb->list); -	cb->file_object = fp; +	cb->fp = fp;  	cb->cl = cl;  	cb->buf_idx = 0;  	cb->fop_type = type; @@ -455,7 +455,8 @@ int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length)   * Return: cb on success and NULL on failure   */  struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length, -				  enum mei_cb_file_ops type, struct file *fp) +				  enum mei_cb_file_ops type, +				  const struct file *fp)  {  	struct mei_cl_cb *cb; @@ -485,7 +486,7 @@ struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl, const struct file *fp)  	struct mei_cl_cb *cb;  	list_for_each_entry(cb, &cl->rd_completed, list) -		if (!fp || fp == cb->file_object) +		if (!fp || fp == cb->fp)  			return cb;  	return NULL; @@ -503,12 +504,12 @@ void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp)  	struct mei_cl_cb *cb, *next;  	list_for_each_entry_safe(cb, next, &cl->rd_completed, list) -		if (!fp || fp == cb->file_object) +		if (!fp || fp == cb->fp)  			mei_io_cb_free(cb);  	list_for_each_entry_safe(cb, next, &cl->rd_pending, list) -		if (!fp || fp == cb->file_object) +		if (!fp || fp == cb->fp)  			mei_io_cb_free(cb);  } @@ -535,7 +536,6 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)  	mei_io_list_flush(&cl->dev->ctrl_wr_list, cl);  	mei_io_list_flush(&cl->dev->ctrl_rd_list, cl);  	mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); -	mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl);  	mei_cl_read_cb_flush(cl, fp); @@ -587,27 +587,23 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev)   * mei_cl_link - allocate host id in the host map   *   * @cl: host client - * @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one   *   * Return: 0 on success   *	-EINVAL on incorrect values   *	-EMFILE if open count exceeded.   */ -int mei_cl_link(struct mei_cl *cl, int id) +int mei_cl_link(struct mei_cl *cl)  {  	struct mei_device *dev;  	long open_handle_count; +	int id;  	if (WARN_ON(!cl || !cl->dev))  		return -EINVAL;  	dev = cl->dev; -	/* If Id is not assigned get one*/ -	if (id == MEI_HOST_CLIENT_ID_ANY) -		id = find_first_zero_bit(dev->host_clients_map, -					MEI_CLIENTS_MAX); - +	id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX);  	if (id >= MEI_CLIENTS_MAX) {  		dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX);  		return -EMFILE; @@ -648,7 +644,7 @@ int mei_cl_unlink(struct mei_cl *cl)  	if (!cl)  		return 0; -	/* wd and amthif might not be initialized */ +	/* amthif might not be initialized */  	if (!cl->dev)  		return 0; @@ -670,31 +666,12 @@ int mei_cl_unlink(struct mei_cl *cl)  	return 0;  } - -void mei_host_client_init(struct work_struct *work) +void mei_host_client_init(struct mei_device *dev)  { -	struct mei_device *dev = -		container_of(work, struct mei_device, init_work); -	struct mei_me_client *me_cl; - -	mutex_lock(&dev->device_lock); - - -	me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid); -	if (me_cl) -		mei_amthif_host_init(dev, me_cl); -	mei_me_cl_put(me_cl); - -	me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid); -	if (me_cl) -		mei_wd_host_init(dev, me_cl); -	mei_me_cl_put(me_cl); -  	dev->dev_state = MEI_DEV_ENABLED;  	dev->reset_count = 0; -	mutex_unlock(&dev->device_lock); -	mei_cl_bus_rescan(dev); +	schedule_work(&dev->bus_rescan_work);  	pm_runtime_mark_last_busy(dev->dev);  	dev_dbg(dev->dev, "rpm: autosuspend\n"); @@ -726,6 +703,33 @@ bool mei_hbuf_acquire(struct mei_device *dev)  }  /** + * mei_cl_wake_all - wake up readers, writers and event waiters so + *                 they can be interrupted + * + * @cl: host client + */ +static void mei_cl_wake_all(struct mei_cl *cl) +{ +	struct mei_device *dev = cl->dev; + +	/* synchronized under device mutex */ +	if (waitqueue_active(&cl->rx_wait)) { +		cl_dbg(dev, cl, "Waking up reading client!\n"); +		wake_up_interruptible(&cl->rx_wait); +	} +	/* synchronized under device mutex */ +	if (waitqueue_active(&cl->tx_wait)) { +		cl_dbg(dev, cl, "Waking up writing client!\n"); +		wake_up_interruptible(&cl->tx_wait); +	} +	/* synchronized under device mutex */ +	if (waitqueue_active(&cl->ev_wait)) { +		cl_dbg(dev, cl, "Waking up waiting for event clients!\n"); +		wake_up_interruptible(&cl->ev_wait); +	} +} + +/**   * mei_cl_set_disconnected - set disconnected state and clear   *   associated states and resources   * @@ -740,8 +744,11 @@ void mei_cl_set_disconnected(struct mei_cl *cl)  		return;  	cl->state = MEI_FILE_DISCONNECTED; +	mei_io_list_free(&dev->write_list, cl); +	mei_io_list_free(&dev->write_waiting_list, cl);  	mei_io_list_flush(&dev->ctrl_rd_list, cl);  	mei_io_list_flush(&dev->ctrl_wr_list, cl); +	mei_cl_wake_all(cl);  	cl->mei_flow_ctrl_creds = 0;  	cl->timer_count = 0; @@ -1034,7 +1041,7 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,   * Return: 0 on success, <0 on failure.   */  int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, -		   struct file *file) +		  const struct file *file)  {  	struct mei_device *dev;  	struct mei_cl_cb *cb; @@ -1119,11 +1126,10 @@ nortpm:   * mei_cl_alloc_linked - allocate and link host client   *   * @dev: the device structure - * @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one   *   * Return: cl on success ERR_PTR on failure   */ -struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id) +struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev)  {  	struct mei_cl *cl;  	int ret; @@ -1134,7 +1140,7 @@ struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id)  		goto err;  	} -	ret = mei_cl_link(cl, id); +	ret = mei_cl_link(cl);  	if (ret)  		goto err; @@ -1149,11 +1155,12 @@ err:  /**   * mei_cl_flow_ctrl_creds - checks flow_control credits for cl.   * - * @cl: private data of the file object + * @cl: host client + * @fp: the file pointer associated with the pointer   *   * Return: 1 if mei_flow_ctrl_creds >0, 0 - otherwise.   */ -int mei_cl_flow_ctrl_creds(struct mei_cl *cl) +static int mei_cl_flow_ctrl_creds(struct mei_cl *cl, const struct file *fp)  {  	int rets; @@ -1164,7 +1171,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)  		return 1;  	if (mei_cl_is_fixed_address(cl)) { -		rets = mei_cl_read_start(cl, mei_cl_mtu(cl), NULL); +		rets = mei_cl_read_start(cl, mei_cl_mtu(cl), fp);  		if (rets && rets != -EBUSY)  			return rets;  		return 1; @@ -1186,7 +1193,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)   *	0 on success   *	-EINVAL when ctrl credits are <= 0   */ -int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) +static int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)  {  	if (WARN_ON(!cl || !cl->me_cl))  		return -EINVAL; @@ -1283,7 +1290,8 @@ int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,   *   * Return: 0 on such and error otherwise.   */ -int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request) +int mei_cl_notify_request(struct mei_cl *cl, +			  const struct file *file, u8 request)  {  	struct mei_device *dev;  	struct mei_cl_cb *cb; @@ -1368,12 +1376,12 @@ void mei_cl_notify(struct mei_cl *cl)  	cl_dbg(dev, cl, "notify event");  	cl->notify_ev = true; -	wake_up_interruptible_all(&cl->ev_wait); +	if (!mei_cl_bus_notify_event(cl)) +		wake_up_interruptible(&cl->ev_wait);  	if (cl->ev_async)  		kill_fasync(&cl->ev_async, SIGIO, POLL_PRI); -	mei_cl_bus_notify_event(cl);  }  /** @@ -1422,6 +1430,25 @@ out:  }  /** + * mei_cl_is_read_fc_cb - check if read cb is waiting for flow control + *                        for given host client + * + * @cl: host client + * + * Return: true, if found at least one cb. + */ +static bool mei_cl_is_read_fc_cb(struct mei_cl *cl) +{ +	struct mei_device *dev = cl->dev; +	struct mei_cl_cb *cb; + +	list_for_each_entry(cb, &dev->ctrl_wr_list.list, list) +		if (cb->fop_type == MEI_FOP_READ && cb->cl == cl) +			return true; +	return false; +} + +/**   * mei_cl_read_start - the start read client message function.   *   * @cl: host client @@ -1430,7 +1457,7 @@ out:   *   * Return: 0 on success, <0 on failure.   */ -int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp) +int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)  {  	struct mei_device *dev;  	struct mei_cl_cb *cb; @@ -1445,7 +1472,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)  		return -ENODEV;  	/* HW currently supports only one pending read */ -	if (!list_empty(&cl->rd_pending)) +	if (!list_empty(&cl->rd_pending) || mei_cl_is_read_fc_cb(cl))  		return -EBUSY;  	if (!mei_me_cl_is_active(cl->me_cl)) { @@ -1524,7 +1551,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,  	first_chunk = cb->buf_idx == 0; -	rets = first_chunk ? mei_cl_flow_ctrl_creds(cl) : 1; +	rets = first_chunk ? mei_cl_flow_ctrl_creds(cl, cb->fp) : 1;  	if (rets < 0)  		return rets; @@ -1556,7 +1583,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,  		return 0;  	} -	cl_dbg(dev, cl, "buf: size = %d idx = %lu\n", +	cl_dbg(dev, cl, "buf: size = %zu idx = %zu\n",  			cb->buf.size, cb->buf_idx);  	rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx); @@ -1618,7 +1645,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)  	if (rets < 0 && rets != -EINPROGRESS) {  		pm_runtime_put_noidle(dev->dev);  		cl_err(dev, cl, "rpm: get failed %d\n", rets); -		return rets; +		goto free;  	}  	cb->buf_idx = 0; @@ -1630,7 +1657,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)  	mei_hdr.msg_complete = 0;  	mei_hdr.internal = cb->internal; -	rets = mei_cl_flow_ctrl_creds(cl); +	rets = mei_cl_flow_ctrl_creds(cl, cb->fp);  	if (rets < 0)  		goto err; @@ -1677,7 +1704,8 @@ out:  		mutex_unlock(&dev->device_lock);  		rets = wait_event_interruptible(cl->tx_wait, -				cl->writing_state == MEI_WRITE_COMPLETE); +				cl->writing_state == MEI_WRITE_COMPLETE || +				(!mei_cl_is_connected(cl)));  		mutex_lock(&dev->device_lock);  		/* wait_event_interruptible returns -ERESTARTSYS */  		if (rets) { @@ -1685,6 +1713,10 @@ out:  				rets = -EINTR;  			goto err;  		} +		if (cl->writing_state != MEI_WRITE_COMPLETE) { +			rets = -EFAULT; +			goto err; +		}  	}  	rets = size; @@ -1692,6 +1724,8 @@ err:  	cl_dbg(dev, cl, "rpm: autosuspend\n");  	pm_runtime_mark_last_busy(dev->dev);  	pm_runtime_put_autosuspend(dev->dev); +free: +	mei_io_cb_free(cb);  	return rets;  } @@ -1721,10 +1755,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)  	case MEI_FOP_READ:  		list_add_tail(&cb->list, &cl->rd_completed); -		if (waitqueue_active(&cl->rx_wait)) -			wake_up_interruptible_all(&cl->rx_wait); -		else -			mei_cl_bus_rx_event(cl); +		if (!mei_cl_bus_rx_event(cl)) +			wake_up_interruptible(&cl->rx_wait);  		break;  	case MEI_FOP_CONNECT: @@ -1753,44 +1785,3 @@ void mei_cl_all_disconnect(struct mei_device *dev)  	list_for_each_entry(cl, &dev->file_list, link)  		mei_cl_set_disconnected(cl);  } - - -/** - * mei_cl_all_wakeup  - wake up all readers and writers they can be interrupted - * - * @dev: mei device - */ -void mei_cl_all_wakeup(struct mei_device *dev) -{ -	struct mei_cl *cl; - -	list_for_each_entry(cl, &dev->file_list, link) { -		if (waitqueue_active(&cl->rx_wait)) { -			cl_dbg(dev, cl, "Waking up reading client!\n"); -			wake_up_interruptible(&cl->rx_wait); -		} -		if (waitqueue_active(&cl->tx_wait)) { -			cl_dbg(dev, cl, "Waking up writing client!\n"); -			wake_up_interruptible(&cl->tx_wait); -		} - -		/* synchronized under device mutex */ -		if (waitqueue_active(&cl->ev_wait)) { -			cl_dbg(dev, cl, "Waking up waiting for event clients!\n"); -			wake_up_interruptible(&cl->ev_wait); -		} -	} -} - -/** - * mei_cl_all_write_clear - clear all pending writes - * - * @dev: mei device - */ -void mei_cl_all_write_clear(struct mei_device *dev) -{ -	mei_io_list_free(&dev->write_list, NULL); -	mei_io_list_free(&dev->write_waiting_list, NULL); -} - - diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 04e1aa39243f..0d7a3a1fef78 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -18,7 +18,6 @@  #define _MEI_CLIENT_H_  #include <linux/types.h> -#include <linux/watchdog.h>  #include <linux/poll.h>  #include <linux/mei.h> @@ -84,7 +83,7 @@ static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl)   * MEI IO Functions   */  struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type, -				 struct file *fp); +				 const struct file *fp);  void mei_io_cb_free(struct mei_cl_cb *priv_cb);  int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length); @@ -108,21 +107,19 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev);  void mei_cl_init(struct mei_cl *cl, struct mei_device *dev); -int mei_cl_link(struct mei_cl *cl, int id); +int mei_cl_link(struct mei_cl *cl);  int mei_cl_unlink(struct mei_cl *cl); -struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id); +struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev);  struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl,  				 const struct file *fp);  void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp);  struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length, -				  enum mei_cb_file_ops type, struct file *fp); +				  enum mei_cb_file_ops type, +				  const struct file *fp);  int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp); -int mei_cl_flow_ctrl_creds(struct mei_cl *cl); - -int mei_cl_flow_ctrl_reduce(struct mei_cl *cl);  /*   *  MEI input output function prototype   */ @@ -217,10 +214,10 @@ void mei_cl_set_disconnected(struct mei_cl *cl);  int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,  			  struct mei_cl_cb *cmpl_list);  int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, -		   struct file *file); +		   const struct file *file);  int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,  			      struct mei_cl_cb *cmpl_list); -int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp); +int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp);  int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr,  			struct mei_cl_cb *cmpl_list);  int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking); @@ -229,19 +226,18 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,  void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb); -void mei_host_client_init(struct work_struct *work); +void mei_host_client_init(struct mei_device *dev);  u8 mei_cl_notify_fop2req(enum mei_cb_file_ops fop);  enum mei_cb_file_ops mei_cl_notify_req2fop(u8 request); -int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request); +int mei_cl_notify_request(struct mei_cl *cl, +			  const struct file *file, u8 request);  int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,  		      struct mei_cl_cb *cmpl_list);  int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev);  void mei_cl_notify(struct mei_cl *cl);  void mei_cl_all_disconnect(struct mei_device *dev); -void mei_cl_all_wakeup(struct mei_device *dev); -void mei_cl_all_write_clear(struct mei_device *dev);  #define MEI_CL_FMT "cl:host=%02d me=%02d "  #define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl) @@ -249,6 +245,9 @@ void mei_cl_all_write_clear(struct mei_device *dev);  #define cl_dbg(dev, cl, format, arg...) \  	dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) +#define cl_warn(dev, cl, format, arg...) \ +	dev_warn((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) +  #define cl_err(dev, cl, format, arg...) \  	dev_err((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index a138d8a27ab5..c6c051b52f55 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -50,6 +50,7 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf,  	}  	pos += scnprintf(buf + pos, bufsz - pos, HDR); +#undef HDR  	/*  if the driver is not enabled the list won't be consistent */  	if (dev->dev_state != MEI_DEV_ENABLED) @@ -90,23 +91,37 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,  {  	struct mei_device *dev = fp->private_data;  	struct mei_cl *cl; -	const size_t bufsz = 1024; +	size_t bufsz = 1;  	char *buf;  	int i = 0;  	int pos = 0;  	int ret; +#define HDR "   |me|host|state|rd|wr|\n" +  	if (!dev)  		return -ENODEV; +	mutex_lock(&dev->device_lock); + +	/* +	 * if the driver is not enabled the list won't be consistent, +	 * we output empty table +	 */ +	if (dev->dev_state == MEI_DEV_ENABLED) +		list_for_each_entry(cl, &dev->file_list, link) +			bufsz++; + +	bufsz *= sizeof(HDR) + 1; +  	buf = kzalloc(bufsz, GFP_KERNEL); -	if  (!buf) +	if  (!buf) { +		mutex_unlock(&dev->device_lock);  		return -ENOMEM; +	} -	pos += scnprintf(buf + pos, bufsz - pos, -			"  |me|host|state|rd|wr|\n"); - -	mutex_lock(&dev->device_lock); +	pos += scnprintf(buf + pos, bufsz - pos, HDR); +#undef HDR  	/*  if the driver is not enabled the list won't be consistent */  	if (dev->dev_state != MEI_DEV_ENABLED) @@ -115,7 +130,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,  	list_for_each_entry(cl, &dev->file_list, link) {  		pos += scnprintf(buf + pos, bufsz - pos, -			"%2d|%2d|%4d|%5d|%2d|%2d|\n", +			"%3d|%2d|%4d|%5d|%2d|%2d|\n",  			i, mei_cl_me_id(cl), cl->host_client_id, cl->state,  			!list_empty(&cl->rd_completed), cl->writing_state);  		i++; @@ -150,16 +165,21 @@ static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf,  	pos += scnprintf(buf + pos, bufsz - pos, "hbm: %s\n",  			mei_hbm_state_str(dev->hbm_state)); -	if (dev->hbm_state == MEI_HBM_STARTED) { +	if (dev->hbm_state >= MEI_HBM_ENUM_CLIENTS && +	    dev->hbm_state <= MEI_HBM_STARTED) {  		pos += scnprintf(buf + pos, bufsz - pos, "hbm features:\n");  		pos += scnprintf(buf + pos, bufsz - pos, "\tPG: %01d\n",  				 dev->hbm_f_pg_supported);  		pos += scnprintf(buf + pos, bufsz - pos, "\tDC: %01d\n",  				 dev->hbm_f_dc_supported); +		pos += scnprintf(buf + pos, bufsz - pos, "\tIE: %01d\n", +				 dev->hbm_f_ie_supported);  		pos += scnprintf(buf + pos, bufsz - pos, "\tDOT: %01d\n",  				 dev->hbm_f_dot_supported);  		pos += scnprintf(buf + pos, bufsz - pos, "\tEV: %01d\n",  				 dev->hbm_f_ev_supported); +		pos += scnprintf(buf + pos, bufsz - pos, "\tFA: %01d\n", +				 dev->hbm_f_fa_supported);  	}  	pos += scnprintf(buf + pos, bufsz - pos, "pg:  %s, %s\n", @@ -175,6 +195,30 @@ static const struct file_operations mei_dbgfs_fops_devstate = {  	.llseek = generic_file_llseek,  }; +static ssize_t mei_dbgfs_write_allow_fa(struct file *file, +					const char __user *user_buf, +					size_t count, loff_t *ppos) +{ +	struct mei_device *dev; +	int ret; + +	dev = container_of(file->private_data, +			   struct mei_device, allow_fixed_address); + +	ret = debugfs_write_file_bool(file, user_buf, count, ppos); +	if (ret < 0) +		return ret; +	dev->override_fixed_address = true; +	return ret; +} + +static const struct file_operations mei_dbgfs_fops_allow_fa = { +	.open = simple_open, +	.read = debugfs_read_file_bool, +	.write = mei_dbgfs_write_allow_fa, +	.llseek = generic_file_llseek, +}; +  /**   * mei_dbgfs_deregister - Remove the debugfs files and directories   * @@ -224,8 +268,9 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name)  		dev_err(dev->dev, "devstate: registration failed\n");  		goto err;  	} -	f = debugfs_create_bool("allow_fixed_address", S_IRUSR | S_IWUSR, dir, -				&dev->allow_fixed_address); +	f = debugfs_create_file("allow_fixed_address", S_IRUSR | S_IWUSR, dir, +				&dev->allow_fixed_address, +				&mei_dbgfs_fops_allow_fa);  	if (!f) {  		dev_err(dev->dev, "allow_fixed_address: registration failed\n");  		goto err; diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index e7b7aad0999b..5e305d2605f3 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -301,7 +301,10 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev)  	enum_req = (struct hbm_host_enum_request *)dev->wr_msg.data;  	memset(enum_req, 0, len);  	enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; -	enum_req->allow_add = dev->hbm_f_dc_supported; +	enum_req->flags |= dev->hbm_f_dc_supported ? +			   MEI_HBM_ENUM_F_ALLOW_ADD : 0; +	enum_req->flags |= dev->hbm_f_ie_supported ? +			   MEI_HBM_ENUM_F_IMMEDIATE_ENUM : 0;  	ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data);  	if (ret) { @@ -401,6 +404,9 @@ static int mei_hbm_fw_add_cl_req(struct mei_device *dev,  	if (ret)  		status = !MEI_HBMS_SUCCESS; +	if (dev->dev_state == MEI_DEV_ENABLED) +		schedule_work(&dev->bus_rescan_work); +  	return mei_hbm_add_cl_resp(dev, req->me_addr, status);  } @@ -543,7 +549,7 @@ static int mei_hbm_prop_req(struct mei_device *dev)  	/* We got all client properties */  	if (next_client_index == MEI_CLIENTS_MAX) {  		dev->hbm_state = MEI_HBM_STARTED; -		schedule_work(&dev->init_work); +		mei_host_client_init(dev);  		return 0;  	} @@ -789,8 +795,11 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, struct mei_cl *cl,  		cl->state = MEI_FILE_CONNECTED;  	else {  		cl->state = MEI_FILE_DISCONNECT_REPLY; -		if (rs->status == MEI_CL_CONN_NOT_FOUND) +		if (rs->status == MEI_CL_CONN_NOT_FOUND) {  			mei_me_cl_del(dev, cl->me_cl); +			if (dev->dev_state == MEI_DEV_ENABLED) +				schedule_work(&dev->bus_rescan_work); +		}  	}  	cl->status = mei_cl_conn_status_to_errno(rs->status);  } @@ -866,7 +875,7 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev,  	cl = mei_hbm_cl_find_by_cmd(dev, disconnect_req);  	if (cl) { -		cl_dbg(dev, cl, "fw disconnect request received\n"); +		cl_warn(dev, cl, "fw disconnect request received\n");  		cl->state = MEI_FILE_DISCONNECTING;  		cl->timer_count = 0; @@ -972,6 +981,9 @@ static void mei_hbm_config_features(struct mei_device *dev)  	if (dev->version.major_version >= HBM_MAJOR_VERSION_DC)  		dev->hbm_f_dc_supported = 1; +	if (dev->version.major_version >= HBM_MAJOR_VERSION_IE) +		dev->hbm_f_ie_supported = 1; +  	/* disconnect on connect timeout instead of link reset */  	if (dev->version.major_version >= HBM_MAJOR_VERSION_DOT)  		dev->hbm_f_dot_supported = 1; @@ -979,6 +991,10 @@ static void mei_hbm_config_features(struct mei_device *dev)  	/* Notification Event Support */  	if (dev->version.major_version >= HBM_MAJOR_VERSION_EV)  		dev->hbm_f_ev_supported = 1; + +	/* Fixed Address Client Support */ +	if (dev->version.major_version >= HBM_MAJOR_VERSION_FA) +		dev->hbm_f_fa_supported = 1;  }  /** diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index a8a68acd3267..0dcb854b4bfc 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -121,6 +121,10 @@  #define MEI_DEV_ID_SPT_2      0x9D3B  /* Sunrise Point 2 */  #define MEI_DEV_ID_SPT_H      0xA13A  /* Sunrise Point H */  #define MEI_DEV_ID_SPT_H_2    0xA13B  /* Sunrise Point H 2 */ + +#define MEI_DEV_ID_BXT_M      0x1A9A  /* Broxton M */ +#define MEI_DEV_ID_APL_I      0x5A9A  /* Apollo Lake I */ +  /*   * MEI HW Section   */ diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 25b1997a62cb..e2fb44cc5c37 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -189,8 +189,11 @@ static int mei_me_fw_status(struct mei_device *dev,  	fw_status->count = fw_src->count;  	for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { -		ret = pci_read_config_dword(pdev, -			fw_src->status[i], &fw_status->status[i]); +		ret = pci_read_config_dword(pdev, fw_src->status[i], +					    &fw_status->status[i]); +		trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X", +				       fw_src->status[i], +				       fw_status->status[i]);  		if (ret)  			return ret;  	} @@ -215,6 +218,7 @@ static void mei_me_hw_config(struct mei_device *dev)  	reg = 0;  	pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); +	trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg);  	hw->d0i3_supported =  		((reg & PCI_CFG_HFS_1_D0I3_MSK) == PCI_CFG_HFS_1_D0I3_MSK); @@ -1248,6 +1252,7 @@ static bool mei_me_fw_type_nm(struct pci_dev *pdev)  	u32 reg;  	pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); +	trace_mei_pci_cfg_read(&pdev->dev, "PCI_CFG_HFS_2", PCI_CFG_HFS_2, reg);  	/* make sure that bit 9 (NM) is up and bit 10 (DM) is down */  	return (reg & 0x600) == 0x200;  } @@ -1260,6 +1265,7 @@ static bool mei_me_fw_type_sps(struct pci_dev *pdev)  	u32 reg;  	/* Read ME FW Status check for SPS Firmware */  	pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); +	trace_mei_pci_cfg_read(&pdev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg);  	/* if bits [19:16] = 15, running SPS Firmware */  	return (reg & 0xf0000) == 0xf0000;  } diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index bae680c648ff..4a6c1b85f11e 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -28,6 +28,9 @@  #include "client.h"  #include "hbm.h" +#include "mei-trace.h" + +  /**   * mei_txe_reg_read - Reads 32bit data from the txe device   * @@ -640,8 +643,11 @@ static int mei_txe_fw_status(struct mei_device *dev,  	fw_status->count = fw_src->count;  	for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { -		ret = pci_read_config_dword(pdev, -			fw_src->status[i], &fw_status->status[i]); +		ret = pci_read_config_dword(pdev, fw_src->status[i], +					    &fw_status->status[i]); +		trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X", +				       fw_src->status[i], +				       fw_status->status[i]);  		if (ret)  			return ret;  	} diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 4cebde85924f..9daf3f9aed25 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -29,7 +29,6 @@  #define MEI_CLIENTS_INIT_TIMEOUT   15  /* HPS: Clients Enumeration Timeout */  #define MEI_IAMTHIF_STALL_TIMER    12  /* HPS */ -#define MEI_IAMTHIF_READ_TIMER     10  /* HPS */  #define MEI_PGI_TIMEOUT             1  /* PG Isolation time response 1 sec */  #define MEI_D0I3_TIMEOUT            5  /* D0i3 set/unset max response time */ @@ -54,6 +53,12 @@  #define HBM_MAJOR_VERSION_DC               2  /* + * MEI version with immediate reply to enum request support + */ +#define HBM_MINOR_VERSION_IE               0 +#define HBM_MAJOR_VERSION_IE               2 + +/*   * MEI version with disconnect on connection timeout support   */  #define HBM_MINOR_VERSION_DOT              0 @@ -65,6 +70,12 @@  #define HBM_MINOR_VERSION_EV               0  #define HBM_MAJOR_VERSION_EV               2 +/* + * MEI version with fixed address client support + */ +#define HBM_MINOR_VERSION_FA               0 +#define HBM_MAJOR_VERSION_FA               2 +  /* Host bus message command opcode */  #define MEI_HBM_CMD_OP_MSK                  0x7f  /* Host bus message command RESPONSE */ @@ -241,15 +252,26 @@ struct hbm_me_stop_request {  } __packed;  /** - * struct hbm_host_enum_request -  enumeration request from host to fw + * enum hbm_host_enum_flags - enumeration request flags (HBM version >= 2.0)   * - * @hbm_cmd: bus message command header - * @allow_add: allow dynamic clients add HBM version >= 2.0 + * @MEI_HBM_ENUM_F_ALLOW_ADD: allow dynamic clients add + * @MEI_HBM_ENUM_F_IMMEDIATE_ENUM: allow FW to send answer immediately + */ +enum hbm_host_enum_flags { +	MEI_HBM_ENUM_F_ALLOW_ADD = BIT(0), +	MEI_HBM_ENUM_F_IMMEDIATE_ENUM = BIT(1), +}; + +/** + * struct hbm_host_enum_request - enumeration request from host to fw + * + * @hbm_cmd : bus message command header + * @flags   : request flags   * @reserved: reserved   */  struct hbm_host_enum_request {  	u8 hbm_cmd; -	u8 allow_add; +	u8 flags;  	u8 reserved[2];  } __packed; diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 3edafc8d3ad4..f7c8dfdb6a12 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -91,8 +91,8 @@ EXPORT_SYMBOL_GPL(mei_fw_status2str);   */  void mei_cancel_work(struct mei_device *dev)  { -	cancel_work_sync(&dev->init_work);  	cancel_work_sync(&dev->reset_work); +	cancel_work_sync(&dev->bus_rescan_work);  	cancel_delayed_work(&dev->timer_work);  } @@ -148,16 +148,10 @@ int mei_reset(struct mei_device *dev)  	    state != MEI_DEV_POWER_UP) {  		/* remove all waiting requests */ -		mei_cl_all_write_clear(dev); -  		mei_cl_all_disconnect(dev); -		/* wake up all readers and writers so they can be interrupted */ -		mei_cl_all_wakeup(dev); -  		/* remove entry if already in list */ -		dev_dbg(dev->dev, "remove iamthif and wd from the file list.\n"); -		mei_cl_unlink(&dev->wd_cl); +		dev_dbg(dev->dev, "remove iamthif from the file list.\n");  		mei_cl_unlink(&dev->iamthif_cl);  		mei_amthif_reset_params(dev);  	} @@ -165,7 +159,6 @@ int mei_reset(struct mei_device *dev)  	mei_hbm_reset(dev);  	dev->rd_msg_hdr = 0; -	dev->wd_pending = false;  	if (ret) {  		dev_err(dev->dev, "hw_reset failed ret = %d\n", ret); @@ -335,16 +328,12 @@ void mei_stop(struct mei_device *dev)  	mutex_lock(&dev->device_lock); -	mei_wd_stop(dev); -  	dev->dev_state = MEI_DEV_POWER_DOWN;  	mei_reset(dev);  	/* move device to disabled state unconditionally */  	dev->dev_state = MEI_DEV_DISABLED;  	mutex_unlock(&dev->device_lock); - -	mei_watchdog_unregister(dev);  }  EXPORT_SYMBOL_GPL(mei_stop); @@ -394,7 +383,6 @@ void mei_device_init(struct mei_device *dev,  	init_waitqueue_head(&dev->wait_hw_ready);  	init_waitqueue_head(&dev->wait_pg);  	init_waitqueue_head(&dev->wait_hbm_start); -	init_waitqueue_head(&dev->wait_stop_wd);  	dev->dev_state = MEI_DEV_INITIALIZING;  	dev->reset_count = 0; @@ -404,13 +392,11 @@ void mei_device_init(struct mei_device *dev,  	mei_io_list_init(&dev->ctrl_rd_list);  	INIT_DELAYED_WORK(&dev->timer_work, mei_timer); -	INIT_WORK(&dev->init_work, mei_host_client_init);  	INIT_WORK(&dev->reset_work, mei_reset_work); +	INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work); -	INIT_LIST_HEAD(&dev->wd_cl.link);  	INIT_LIST_HEAD(&dev->iamthif_cl.link);  	mei_io_list_init(&dev->amthif_cmd_list); -	mei_io_list_init(&dev->amthif_rd_complete_list);  	bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);  	dev->open_handle_count = 0; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 64b568a0268d..1e5cb1f704f8 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -48,7 +48,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *compl_list)  		dev_dbg(dev->dev, "completing call back.\n");  		if (cl == &dev->iamthif_cl) -			mei_amthif_complete(dev, cb); +			mei_amthif_complete(cl, cb);  		else  			mei_cl_complete(cl, cb);  	} @@ -104,6 +104,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,  	struct mei_device *dev = cl->dev;  	struct mei_cl_cb *cb;  	unsigned char *buffer = NULL; +	size_t buf_sz;  	cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list);  	if (!cb) { @@ -124,11 +125,21 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,  		goto out;  	} -	if (cb->buf.size < mei_hdr->length + cb->buf_idx) { -		cl_dbg(dev, cl, "message overflow. size %d len %d idx %ld\n", +	buf_sz = mei_hdr->length + cb->buf_idx; +	/* catch for integer overflow */ +	if (buf_sz < cb->buf_idx) { +		cl_err(dev, cl, "message is too big len %d idx %zu\n", +		       mei_hdr->length, cb->buf_idx); + +		list_move_tail(&cb->list, &complete_list->list); +		cb->status = -EMSGSIZE; +		goto out; +	} + +	if (cb->buf.size < buf_sz) { +		cl_dbg(dev, cl, "message overflow. size %zu len %d idx %zu\n",  			cb->buf.size, mei_hdr->length, cb->buf_idx); -		buffer = krealloc(cb->buf.data, mei_hdr->length + cb->buf_idx, -				  GFP_KERNEL); +		buffer = krealloc(cb->buf.data, buf_sz, GFP_KERNEL);  		if (!buffer) {  			cb->status = -ENOMEM; @@ -136,7 +147,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,  			goto out;  		}  		cb->buf.data = buffer; -		cb->buf.size = mei_hdr->length + cb->buf_idx; +		cb->buf.size = buf_sz;  	}  	buffer = cb->buf.data + cb->buf_idx; @@ -145,8 +156,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,  	cb->buf_idx += mei_hdr->length;  	if (mei_hdr->msg_complete) { -		cb->read_time = jiffies; -		cl_dbg(dev, cl, "completed read length = %lu\n", cb->buf_idx); +		cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx);  		list_move_tail(&cb->list, &complete_list->list);  	} else {  		pm_runtime_mark_last_busy(dev->dev); @@ -229,6 +239,16 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,  	return 0;  } +static inline bool hdr_is_hbm(struct mei_msg_hdr *mei_hdr) +{ +	return mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0; +} + +static inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr) +{ +	return mei_hdr->host_addr == 0 && mei_hdr->me_addr != 0; +} +  /**   * mei_irq_read_handler - bottom half read routine after ISR to   * handle the read processing. @@ -270,7 +290,7 @@ int mei_irq_read_handler(struct mei_device *dev,  	}  	/*  HBM message */ -	if (mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0) { +	if (hdr_is_hbm(mei_hdr)) {  		ret = mei_hbm_dispatch(dev, mei_hdr);  		if (ret) {  			dev_dbg(dev->dev, "mei_hbm_dispatch failed ret = %d\n", @@ -290,6 +310,14 @@ int mei_irq_read_handler(struct mei_device *dev,  	/* if no recipient cl was found we assume corrupted header */  	if (&cl->link == &dev->file_list) { +		/* A message for not connected fixed address clients +		 * should be silently discarded +		 */ +		if (hdr_is_fixed(mei_hdr)) { +			mei_irq_discard_msg(dev, mei_hdr); +			ret = 0; +			goto reset_slots; +		}  		dev_err(dev->dev, "no destination client found 0x%08X\n",  				dev->rd_msg_hdr);  		ret = -EBADMSG; @@ -360,21 +388,6 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)  		list_move_tail(&cb->list, &cmpl_list->list);  	} -	if (dev->wd_state == MEI_WD_STOPPING) { -		dev->wd_state = MEI_WD_IDLE; -		wake_up(&dev->wait_stop_wd); -	} - -	if (mei_cl_is_connected(&dev->wd_cl)) { -		if (dev->wd_pending && -		    mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) { -			ret = mei_wd_send(dev); -			if (ret) -				return ret; -			dev->wd_pending = false; -		} -	} -  	/* complete control write list CB */  	dev_dbg(dev->dev, "complete control write list cb.\n");  	list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list.list, list) { @@ -462,7 +475,6 @@ static void mei_connect_timeout(struct mei_cl *cl)   */  void mei_timer(struct work_struct *work)  { -	unsigned long timeout;  	struct mei_cl *cl;  	struct mei_device *dev = container_of(work, @@ -508,45 +520,15 @@ void mei_timer(struct work_struct *work)  			mei_reset(dev);  			dev->iamthif_canceled = false;  			dev->iamthif_state = MEI_IAMTHIF_IDLE; -			dev->iamthif_timer = 0;  			mei_io_cb_free(dev->iamthif_current_cb);  			dev->iamthif_current_cb = NULL; -			dev->iamthif_file_object = NULL; +			dev->iamthif_fp = NULL;  			mei_amthif_run_next_cmd(dev);  		}  	} -	if (dev->iamthif_timer) { - -		timeout = dev->iamthif_timer + -			mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); - -		dev_dbg(dev->dev, "dev->iamthif_timer = %ld\n", -				dev->iamthif_timer); -		dev_dbg(dev->dev, "timeout = %ld\n", timeout); -		dev_dbg(dev->dev, "jiffies = %ld\n", jiffies); -		if (time_after(jiffies, timeout)) { -			/* -			 * User didn't read the AMTHI data on time (15sec) -			 * freeing AMTHI for other requests -			 */ - -			dev_dbg(dev->dev, "freeing AMTHI for other requests\n"); - -			mei_io_list_flush(&dev->amthif_rd_complete_list, -				&dev->iamthif_cl); -			mei_io_cb_free(dev->iamthif_current_cb); -			dev->iamthif_current_cb = NULL; - -			dev->iamthif_file_object->private_data = NULL; -			dev->iamthif_file_object = NULL; -			dev->iamthif_timer = 0; -			mei_amthif_run_next_cmd(dev); - -		} -	}  out:  	if (dev->dev_state != MEI_DEV_DISABLED)  		schedule_delayed_work(&dev->timer_work, 2 * HZ); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 677d0362f334..52635b063873 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -65,7 +65,7 @@ static int mei_open(struct inode *inode, struct file *file)  		goto err_unlock;  	} -	cl = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); +	cl = mei_cl_alloc_linked(dev);  	if (IS_ERR(cl)) {  		err = PTR_ERR(cl);  		goto err_unlock; @@ -159,27 +159,22 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,  		goto out;  	} +	if (ubuf == NULL) { +		rets = -EMSGSIZE; +		goto out; +	} +  	if (cl == &dev->iamthif_cl) {  		rets = mei_amthif_read(dev, file, ubuf, length, offset);  		goto out;  	}  	cb = mei_cl_read_cb(cl, file); -	if (cb) { -		/* read what left */ -		if (cb->buf_idx > *offset) -			goto copy_buffer; -		/* offset is beyond buf_idx we have no more data return 0 */ -		if (cb->buf_idx > 0 && cb->buf_idx <= *offset) { -			rets = 0; -			goto free; -		} -		/* Offset needs to be cleaned for contiguous reads*/ -		if (cb->buf_idx == 0 && *offset > 0) -			*offset = 0; -	} else if (*offset > 0) { +	if (cb) +		goto copy_buffer; + +	if (*offset > 0)  		*offset = 0; -	}  	err = mei_cl_read_start(cl, length, file);  	if (err && err != -EBUSY) { @@ -214,11 +209,6 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,  	cb = mei_cl_read_cb(cl, file);  	if (!cb) { -		if (mei_cl_is_fixed_address(cl) && dev->allow_fixed_address) { -			cb = mei_cl_read_cb(cl, NULL); -			if (cb) -				goto copy_buffer; -		}  		rets = 0;  		goto out;  	} @@ -231,10 +221,10 @@ copy_buffer:  		goto free;  	} -	cl_dbg(dev, cl, "buf.size = %d buf.idx = %ld\n", -	    cb->buf.size, cb->buf_idx); -	if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) { -		rets = -EMSGSIZE; +	cl_dbg(dev, cl, "buf.size = %zu buf.idx = %zu offset = %lld\n", +	       cb->buf.size, cb->buf_idx, *offset); +	if (*offset >= cb->buf_idx) { +		rets = 0;  		goto free;  	} @@ -250,11 +240,13 @@ copy_buffer:  	rets = length;  	*offset += length; -	if ((unsigned long)*offset < cb->buf_idx) +	/* not all data was read, keep the cb */ +	if (*offset < cb->buf_idx)  		goto out;  free:  	mei_io_cb_free(cb); +	*offset = 0;  out:  	cl_dbg(dev, cl, "end mei read rets = %d\n", rets); @@ -275,9 +267,8 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,  			 size_t length, loff_t *offset)  {  	struct mei_cl *cl = file->private_data; -	struct mei_cl_cb *write_cb = NULL; +	struct mei_cl_cb *cb;  	struct mei_device *dev; -	unsigned long timeout = 0;  	int rets;  	if (WARN_ON(!cl || !cl->dev)) @@ -313,52 +304,31 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,  		goto out;  	} -	if (cl == &dev->iamthif_cl) { -		write_cb = mei_amthif_find_read_list_entry(dev, file); - -		if (write_cb) { -			timeout = write_cb->read_time + -				mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); - -			if (time_after(jiffies, timeout)) { -				*offset = 0; -				mei_io_cb_free(write_cb); -				write_cb = NULL; -			} -		} -	} -  	*offset = 0; -	write_cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file); -	if (!write_cb) { +	cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file); +	if (!cb) {  		rets = -ENOMEM;  		goto out;  	} -	rets = copy_from_user(write_cb->buf.data, ubuf, length); +	rets = copy_from_user(cb->buf.data, ubuf, length);  	if (rets) {  		dev_dbg(dev->dev, "failed to copy data from userland\n");  		rets = -EFAULT; +		mei_io_cb_free(cb);  		goto out;  	}  	if (cl == &dev->iamthif_cl) { -		rets = mei_amthif_write(cl, write_cb); - -		if (rets) { -			dev_err(dev->dev, -				"amthif write failed with status = %d\n", rets); -			goto out; -		} -		mutex_unlock(&dev->device_lock); -		return length; +		rets = mei_amthif_write(cl, cb); +		if (!rets) +			rets = length; +		goto out;  	} -	rets = mei_cl_write(cl, write_cb, false); +	rets = mei_cl_write(cl, cb, false);  out:  	mutex_unlock(&dev->device_lock); -	if (rets < 0) -		mei_io_cb_free(write_cb);  	return rets;  } @@ -393,12 +363,22 @@ static int mei_ioctl_connect_client(struct file *file,  	/* find ME client we're trying to connect to */  	me_cl = mei_me_cl_by_uuid(dev, &data->in_client_uuid); -	if (!me_cl || -	    (me_cl->props.fixed_address && !dev->allow_fixed_address)) { +	if (!me_cl) {  		dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",  			&data->in_client_uuid); -		mei_me_cl_put(me_cl); -		return  -ENOTTY; +		rets = -ENOTTY; +		goto end; +	} + +	if (me_cl->props.fixed_address) { +		bool forbidden = dev->override_fixed_address ? +			 !dev->allow_fixed_address : !dev->hbm_f_fa_supported; +		if (forbidden) { +			dev_dbg(dev->dev, "Connection forbidden to FW Client UUID = %pUl\n", +				&data->in_client_uuid); +			rets = -ENOTTY; +			goto end; +		}  	}  	dev_dbg(dev->dev, "Connect to FW Client ID = %d\n", @@ -454,11 +434,15 @@ end:   *   * Return: 0 on success , <0 on error   */ -static int mei_ioctl_client_notify_request(struct file *file, u32 request) +static int mei_ioctl_client_notify_request(const struct file *file, u32 request)  {  	struct mei_cl *cl = file->private_data; -	return mei_cl_notify_request(cl, file, request); +	if (request != MEI_HBM_NOTIFICATION_START && +	    request != MEI_HBM_NOTIFICATION_STOP) +		return -EINVAL; + +	return mei_cl_notify_request(cl, file, (u8)request);  }  /** @@ -469,7 +453,7 @@ static int mei_ioctl_client_notify_request(struct file *file, u32 request)   *   * Return: 0 on success , <0 on error   */ -static int mei_ioctl_client_notify_get(struct file *file, u32 *notify_get) +static int mei_ioctl_client_notify_get(const struct file *file, u32 *notify_get)  {  	struct mei_cl *cl = file->private_data;  	bool notify_ev; diff --git a/drivers/misc/mei/mei-trace.c b/drivers/misc/mei/mei-trace.c index 388efb519138..e19e6acb191b 100644 --- a/drivers/misc/mei/mei-trace.c +++ b/drivers/misc/mei/mei-trace.c @@ -22,4 +22,6 @@  EXPORT_TRACEPOINT_SYMBOL(mei_reg_read);  EXPORT_TRACEPOINT_SYMBOL(mei_reg_write); +EXPORT_TRACEPOINT_SYMBOL(mei_pci_cfg_read); +EXPORT_TRACEPOINT_SYMBOL(mei_pci_cfg_write);  #endif /* __CHECKER__ */ diff --git a/drivers/misc/mei/mei-trace.h b/drivers/misc/mei/mei-trace.h index 47e1bc6551d4..7d2d5d4a1624 100644 --- a/drivers/misc/mei/mei-trace.h +++ b/drivers/misc/mei/mei-trace.h @@ -60,7 +60,45 @@ TRACE_EVENT(mei_reg_write,  		__entry->offs = offs;  		__entry->val = val;  	), -	TP_printk("[%s] write %s[%#x] = %#x)", +	TP_printk("[%s] write %s[%#x] = %#x", +		  __get_str(dev), __entry->reg,  __entry->offs, __entry->val) +); + +TRACE_EVENT(mei_pci_cfg_read, +	TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val), +	TP_ARGS(dev, reg, offs, val), +	TP_STRUCT__entry( +		__string(dev, dev_name(dev)) +		__field(const char *, reg) +		__field(u32, offs) +		__field(u32, val) +	), +	TP_fast_assign( +		__assign_str(dev, dev_name(dev)) +		__entry->reg  = reg; +		__entry->offs = offs; +		__entry->val = val; +	), +	TP_printk("[%s] pci cfg read %s:[%#x] = %#x", +		  __get_str(dev), __entry->reg, __entry->offs, __entry->val) +); + +TRACE_EVENT(mei_pci_cfg_write, +	TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val), +	TP_ARGS(dev, reg, offs, val), +	TP_STRUCT__entry( +		__string(dev, dev_name(dev)) +		__field(const char *, reg) +		__field(u32, offs) +		__field(u32, val) +	), +	TP_fast_assign( +		__assign_str(dev, dev_name(dev)) +		__entry->reg = reg; +		__entry->offs = offs; +		__entry->val = val; +	), +	TP_printk("[%s] pci cfg write %s[%#x] = %#x",  		  __get_str(dev), __entry->reg,  __entry->offs, __entry->val)  ); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 4250555d5e72..db78e6d99456 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -18,7 +18,7 @@  #define _MEI_DEV_H_  #include <linux/types.h> -#include <linux/watchdog.h> +#include <linux/cdev.h>  #include <linux/poll.h>  #include <linux/mei.h>  #include <linux/mei_cl_bus.h> @@ -26,33 +26,13 @@  #include "hw.h"  #include "hbm.h" -/* - * watch dog definition - */ -#define MEI_WD_HDR_SIZE       4 -#define MEI_WD_STOP_MSG_SIZE  MEI_WD_HDR_SIZE -#define MEI_WD_START_MSG_SIZE (MEI_WD_HDR_SIZE + 16) - -#define MEI_WD_DEFAULT_TIMEOUT   120  /* seconds */ -#define MEI_WD_MIN_TIMEOUT       120  /* seconds */ -#define MEI_WD_MAX_TIMEOUT     65535  /* seconds */ - -#define MEI_WD_STOP_TIMEOUT      10 /* msecs */ - -#define MEI_WD_STATE_INDEPENDENCE_MSG_SENT       (1 << 0) - -#define MEI_RD_MSG_BUF_SIZE           (128 * sizeof(u32)) -  /*   * AMTHI Client UUID   */  extern const uuid_le mei_amthif_guid; -/* - * Watchdog Client UUID - */ -extern const uuid_le mei_wd_guid; +#define MEI_RD_MSG_BUF_SIZE           (128 * sizeof(u32))  /*   * Number of Maximum MEI Clients @@ -73,15 +53,6 @@ extern const uuid_le mei_wd_guid;   */  #define  MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 1) -/* - * Internal Clients Number - */ -#define MEI_HOST_CLIENT_ID_ANY        (-1) -#define MEI_HBM_HOST_CLIENT_ID         0 /* not used, just for documentation */ -#define MEI_WD_HOST_CLIENT_ID          1 -#define MEI_IAMTHIF_HOST_CLIENT_ID     2 - -  /* File state */  enum file_state {  	MEI_FILE_INITIALIZING = 0, @@ -123,12 +94,6 @@ enum mei_file_transaction_states {  	MEI_READ_COMPLETE  }; -enum mei_wd_states { -	MEI_WD_IDLE, -	MEI_WD_RUNNING, -	MEI_WD_STOPPING, -}; -  /**   * enum mei_cb_file_ops  - file operation associated with the callback   * @MEI_FOP_READ:       read @@ -153,7 +118,7 @@ enum mei_cb_file_ops {   * Intel MEI message data struct   */  struct mei_msg_data { -	u32 size; +	size_t size;  	unsigned char *data;  }; @@ -206,8 +171,7 @@ struct mei_cl;   * @fop_type: file operation type   * @buf: buffer for data associated with the callback   * @buf_idx: last read index - * @read_time: last read operation time stamp (iamthif) - * @file_object: pointer to file structure + * @fp: pointer to file structure   * @status: io status of the cb   * @internal: communication between driver and FW flag   * @completed: the transfer or reception has completed @@ -217,9 +181,8 @@ struct mei_cl_cb {  	struct mei_cl *cl;  	enum mei_cb_file_ops fop_type;  	struct mei_msg_data buf; -	unsigned long buf_idx; -	unsigned long read_time; -	struct file *file_object; +	size_t buf_idx; +	const struct file *fp;  	int status;  	u32 internal:1;  	u32 completed:1; @@ -341,12 +304,13 @@ struct mei_hw_ops {  /* MEI bus API*/  void mei_cl_bus_rescan(struct mei_device *bus); +void mei_cl_bus_rescan_work(struct work_struct *work);  void mei_cl_bus_dev_fixup(struct mei_cl_device *dev);  ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,  			bool blocking);  ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length); -void mei_cl_bus_rx_event(struct mei_cl *cl); -void mei_cl_bus_notify_event(struct mei_cl *cl); +bool mei_cl_bus_rx_event(struct mei_cl *cl); +bool mei_cl_bus_notify_event(struct mei_cl *cl);  void mei_cl_bus_remove_devices(struct mei_device *bus);  int mei_cl_bus_init(void);  void mei_cl_bus_exit(void); @@ -404,7 +368,6 @@ const char *mei_pg_state_str(enum mei_pg_state state);   * @wait_hw_ready : wait queue for receive HW ready message form FW   * @wait_pg     : wait queue for receive PG message from FW   * @wait_hbm_start : wait queue for receive HBM start message from FW - * @wait_stop_wd : wait queue for receive WD stop message from FW   *   * @reset_count : number of consecutive resets   * @dev_state   : device state @@ -426,6 +389,8 @@ const char *mei_pg_state_str(enum mei_pg_state state);   * @hbm_f_dc_supported  : hbm feature dynamic clients   * @hbm_f_dot_supported : hbm feature disconnect on timeout   * @hbm_f_ev_supported  : hbm feature event notification + * @hbm_f_fa_supported  : hbm feature fixed address client + * @hbm_f_ie_supported  : hbm feature immediate reply to enum request   *   * @me_clients_rwsem: rw lock over me_clients list   * @me_clients  : list of FW clients @@ -434,26 +399,19 @@ const char *mei_pg_state_str(enum mei_pg_state state);   * @me_client_index : last FW client index in enumeration   *   * @allow_fixed_address: allow user space to connect a fixed client - * - * @wd_cl       : watchdog client - * @wd_state    : watchdog client state - * @wd_pending  : watchdog command is pending - * @wd_timeout  : watchdog expiration timeout - * @wd_data     : watchdog message buffer + * @override_fixed_address: force allow fixed address behavior   *   * @amthif_cmd_list : amthif list for cmd waiting - * @amthif_rd_complete_list : amthif list for reading completed cmd data - * @iamthif_file_object : file for current amthif operation + * @iamthif_fp : file for current amthif operation   * @iamthif_cl  : amthif host client   * @iamthif_current_cb : amthif current operation callback   * @iamthif_open_count : number of opened amthif connections - * @iamthif_timer : time stamp of current amthif command completion   * @iamthif_stall_timer : timer to detect amthif hang   * @iamthif_state : amthif processor state   * @iamthif_canceled : current amthif command is canceled   * - * @init_work   : work item for the device init   * @reset_work  : work item for the device reset + * @bus_rescan_work : work item for the bus rescan   *   * @device_list : mei client bus list   * @cl_bus_lock : client bus list lock @@ -486,7 +444,6 @@ struct mei_device {  	wait_queue_head_t wait_hw_ready;  	wait_queue_head_t wait_pg;  	wait_queue_head_t wait_hbm_start; -	wait_queue_head_t wait_stop_wd;  	/*  	 * mei device  states @@ -522,6 +479,8 @@ struct mei_device {  	unsigned int hbm_f_dc_supported:1;  	unsigned int hbm_f_dot_supported:1;  	unsigned int hbm_f_ev_supported:1; +	unsigned int hbm_f_fa_supported:1; +	unsigned int hbm_f_ie_supported:1;  	struct rw_semaphore me_clients_rwsem;  	struct list_head me_clients; @@ -530,29 +489,21 @@ struct mei_device {  	unsigned long me_client_index;  	bool allow_fixed_address; - -	struct mei_cl wd_cl; -	enum mei_wd_states wd_state; -	bool wd_pending; -	u16 wd_timeout; -	unsigned char wd_data[MEI_WD_START_MSG_SIZE]; - +	bool override_fixed_address;  	/* amthif list for cmd waiting */  	struct mei_cl_cb amthif_cmd_list;  	/* driver managed amthif list for reading completed amthif cmd data */ -	struct mei_cl_cb amthif_rd_complete_list; -	struct file *iamthif_file_object; +	const struct file *iamthif_fp;  	struct mei_cl iamthif_cl;  	struct mei_cl_cb *iamthif_current_cb;  	long iamthif_open_count; -	unsigned long iamthif_timer;  	u32 iamthif_stall_timer;  	enum iamthif_states iamthif_state;  	bool iamthif_canceled; -	struct work_struct init_work;  	struct work_struct reset_work; +	struct work_struct bus_rescan_work;  	/* List of bus devices */  	struct list_head device_list; @@ -635,47 +586,18 @@ unsigned int mei_amthif_poll(struct mei_device *dev,  int mei_amthif_release(struct mei_device *dev, struct file *file); -struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, -						struct file *file); -  int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb);  int mei_amthif_run_next_cmd(struct mei_device *dev);  int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,  			struct mei_cl_cb *cmpl_list); -void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb); +void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb);  int mei_amthif_irq_read_msg(struct mei_cl *cl,  			    struct mei_msg_hdr *mei_hdr,  			    struct mei_cl_cb *complete_list);  int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);  /* - * NFC functions - */ -int mei_nfc_host_init(struct mei_device *dev, struct mei_me_client *me_cl); -void mei_nfc_host_exit(struct mei_device *dev); - -/* - * NFC Client UUID - */ -extern const uuid_le mei_nfc_guid; - -int mei_wd_send(struct mei_device *dev); -int mei_wd_stop(struct mei_device *dev); -int mei_wd_host_init(struct mei_device *dev, struct mei_me_client *me_cl); -/* - * mei_watchdog_register  - Registering watchdog interface - *   once we got connection to the WD Client - * @dev: mei device - */ -int mei_watchdog_register(struct mei_device *dev); -/* - * mei_watchdog_unregister  - Unregistering watchdog interface - * @dev: mei device - */ -void mei_watchdog_unregister(struct mei_device *dev); - -/*   * Register Access Function   */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 75fc9c688df8..64e64da6da44 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -88,6 +88,9 @@ static const struct pci_device_id mei_me_pci_tbl[] = {  	{MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, mei_me_pch8_cfg)},  	{MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, mei_me_pch8_cfg)}, +	{MEI_PCI_DEVICE(MEI_DEV_ID_BXT_M, mei_me_pch8_cfg)}, +	{MEI_PCI_DEVICE(MEI_DEV_ID_APL_I, mei_me_pch8_cfg)}, +  	/* required last entry */  	{0, }  }; @@ -210,7 +213,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)  	err = mei_register(dev, &pdev->dev);  	if (err) -		goto release_irq; +		goto stop;  	pci_set_drvdata(pdev, dev); @@ -231,6 +234,8 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)  	return 0; +stop: +	mei_stop(dev);  release_irq:  	mei_cancel_work(dev);  	mei_disable_interrupts(dev); diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index 71f8a7475717..30cc30683c07 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -154,7 +154,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)  	err = mei_register(dev, &pdev->dev);  	if (err) -		goto release_irq; +		goto stop;  	pci_set_drvdata(pdev, dev); @@ -170,6 +170,8 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)  	return 0; +stop: +	mei_stop(dev);  release_irq:  	mei_cancel_work(dev); diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c deleted file mode 100644 index b346638833b0..000000000000 --- a/drivers/misc/mei/wd.c +++ /dev/null @@ -1,391 +0,0 @@ -/* - * - * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-2012, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for - * more details. - * - */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/device.h> -#include <linux/sched.h> -#include <linux/watchdog.h> - -#include <linux/mei.h> - -#include "mei_dev.h" -#include "hbm.h" -#include "client.h" - -static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; -static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 }; - -/* - * AMT Watchdog Device - */ -#define INTEL_AMT_WATCHDOG_ID "INTCAMT" - -/* UUIDs for AMT F/W clients */ -const uuid_le mei_wd_guid = UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, 0x89, -						0x9D, 0xA9, 0x15, 0x14, 0xCB, -						0x32, 0xAB); - -static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) -{ -	dev_dbg(dev->dev, "wd: set timeout=%d.\n", timeout); -	memcpy(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE); -	memcpy(dev->wd_data + MEI_WD_HDR_SIZE, &timeout, sizeof(u16)); -} - -/** - * mei_wd_host_init - connect to the watchdog client - * - * @dev: the device structure - * @me_cl: me client - * - * Return: -ENOTTY if wd client cannot be found - *         -EIO if write has failed - *         0 on success - */ -int mei_wd_host_init(struct mei_device *dev, struct mei_me_client *me_cl) -{ -	struct mei_cl *cl = &dev->wd_cl; -	int ret; - -	mei_cl_init(cl, dev); - -	dev->wd_timeout = MEI_WD_DEFAULT_TIMEOUT; -	dev->wd_state = MEI_WD_IDLE; - -	ret = mei_cl_link(cl, MEI_WD_HOST_CLIENT_ID); -	if (ret < 0) { -		dev_info(dev->dev, "wd: failed link client\n"); -		return ret; -	} - -	ret = mei_cl_connect(cl, me_cl, NULL); -	if (ret) { -		dev_err(dev->dev, "wd: failed to connect = %d\n", ret); -		mei_cl_unlink(cl); -		return ret; -	} - -	ret = mei_watchdog_register(dev); -	if (ret) { -		mei_cl_disconnect(cl); -		mei_cl_unlink(cl); -	} -	return ret; -} - -/** - * mei_wd_send - sends watch dog message to fw. - * - * @dev: the device structure - * - * Return: 0 if success, - *	-EIO when message send fails - *	-EINVAL when invalid message is to be sent - *	-ENODEV on flow control failure - */ -int mei_wd_send(struct mei_device *dev) -{ -	struct mei_cl *cl = &dev->wd_cl; -	struct mei_msg_hdr hdr; -	int ret; - -	hdr.host_addr = cl->host_client_id; -	hdr.me_addr = mei_cl_me_id(cl); -	hdr.msg_complete = 1; -	hdr.reserved = 0; -	hdr.internal = 0; - -	if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE)) -		hdr.length = MEI_WD_START_MSG_SIZE; -	else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_HDR_SIZE)) -		hdr.length = MEI_WD_STOP_MSG_SIZE; -	else { -		dev_err(dev->dev, "wd: invalid message is to be sent, aborting\n"); -		return -EINVAL; -	} - -	ret = mei_write_message(dev, &hdr, dev->wd_data); -	if (ret) { -		dev_err(dev->dev, "wd: write message failed\n"); -		return ret; -	} - -	ret = mei_cl_flow_ctrl_reduce(cl); -	if (ret) { -		dev_err(dev->dev, "wd: flow_ctrl_reduce failed.\n"); -		return ret; -	} - -	return 0; -} - -/** - * mei_wd_stop - sends watchdog stop message to fw. - * - * @dev: the device structure - * - * Return: 0 if success - * on error: - *	-EIO    when message send fails - *	-EINVAL when invalid message is to be sent - *	-ETIME  on message timeout - */ -int mei_wd_stop(struct mei_device *dev) -{ -	struct mei_cl *cl = &dev->wd_cl; -	int ret; - -	if (!mei_cl_is_connected(cl) || -	    dev->wd_state != MEI_WD_RUNNING) -		return 0; - -	memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_STOP_MSG_SIZE); - -	dev->wd_state = MEI_WD_STOPPING; - -	ret = mei_cl_flow_ctrl_creds(cl); -	if (ret < 0) -		goto err; - -	if (ret && mei_hbuf_acquire(dev)) { -		ret = mei_wd_send(dev); -		if (ret) -			goto err; -		dev->wd_pending = false; -	} else { -		dev->wd_pending = true; -	} - -	mutex_unlock(&dev->device_lock); - -	ret = wait_event_timeout(dev->wait_stop_wd, -				dev->wd_state == MEI_WD_IDLE, -				msecs_to_jiffies(MEI_WD_STOP_TIMEOUT)); -	mutex_lock(&dev->device_lock); -	if (dev->wd_state != MEI_WD_IDLE) { -		/* timeout */ -		ret = -ETIME; -		dev_warn(dev->dev, "wd: stop failed to complete ret=%d\n", ret); -		goto err; -	} -	dev_dbg(dev->dev, "wd: stop completed after %u msec\n", -			MEI_WD_STOP_TIMEOUT - jiffies_to_msecs(ret)); -	return 0; -err: -	return ret; -} - -/** - * mei_wd_ops_start - wd start command from the watchdog core. - * - * @wd_dev: watchdog device struct - * - * Return: 0 if success, negative errno code for failure - */ -static int mei_wd_ops_start(struct watchdog_device *wd_dev) -{ -	struct mei_device *dev; -	struct mei_cl *cl; -	int err = -ENODEV; - -	dev = watchdog_get_drvdata(wd_dev); -	if (!dev) -		return -ENODEV; - -	cl = &dev->wd_cl; - -	mutex_lock(&dev->device_lock); - -	if (dev->dev_state != MEI_DEV_ENABLED) { -		dev_dbg(dev->dev, "wd: dev_state != MEI_DEV_ENABLED  dev_state = %s\n", -			mei_dev_state_str(dev->dev_state)); -		goto end_unlock; -	} - -	if (!mei_cl_is_connected(cl)) { -		cl_dbg(dev, cl, "MEI Driver is not connected to Watchdog Client\n"); -		goto end_unlock; -	} - -	mei_wd_set_start_timeout(dev, dev->wd_timeout); - -	err = 0; -end_unlock: -	mutex_unlock(&dev->device_lock); -	return err; -} - -/** - * mei_wd_ops_stop -  wd stop command from the watchdog core. - * - * @wd_dev: watchdog device struct - * - * Return: 0 if success, negative errno code for failure - */ -static int mei_wd_ops_stop(struct watchdog_device *wd_dev) -{ -	struct mei_device *dev; - -	dev = watchdog_get_drvdata(wd_dev); -	if (!dev) -		return -ENODEV; - -	mutex_lock(&dev->device_lock); -	mei_wd_stop(dev); -	mutex_unlock(&dev->device_lock); - -	return 0; -} - -/** - * mei_wd_ops_ping - wd ping command from the watchdog core. - * - * @wd_dev: watchdog device struct - * - * Return: 0 if success, negative errno code for failure - */ -static int mei_wd_ops_ping(struct watchdog_device *wd_dev) -{ -	struct mei_device *dev; -	struct mei_cl *cl; -	int ret; - -	dev = watchdog_get_drvdata(wd_dev); -	if (!dev) -		return -ENODEV; - -	cl = &dev->wd_cl; - -	mutex_lock(&dev->device_lock); - -	if (!mei_cl_is_connected(cl)) { -		cl_err(dev, cl, "wd: not connected.\n"); -		ret = -ENODEV; -		goto end; -	} - -	dev->wd_state = MEI_WD_RUNNING; - -	ret = mei_cl_flow_ctrl_creds(cl); -	if (ret < 0) -		goto end; - -	/* Check if we can send the ping to HW*/ -	if (ret && mei_hbuf_acquire(dev)) { -		dev_dbg(dev->dev, "wd: sending ping\n"); - -		ret = mei_wd_send(dev); -		if (ret) -			goto end; -		dev->wd_pending = false; -	} else { -		dev->wd_pending = true; -	} - -end: -	mutex_unlock(&dev->device_lock); -	return ret; -} - -/** - * mei_wd_ops_set_timeout - wd set timeout command from the watchdog core. - * - * @wd_dev: watchdog device struct - * @timeout: timeout value to set - * - * Return: 0 if success, negative errno code for failure - */ -static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev, -		unsigned int timeout) -{ -	struct mei_device *dev; - -	dev = watchdog_get_drvdata(wd_dev); -	if (!dev) -		return -ENODEV; - -	/* Check Timeout value */ -	if (timeout < MEI_WD_MIN_TIMEOUT || timeout > MEI_WD_MAX_TIMEOUT) -		return -EINVAL; - -	mutex_lock(&dev->device_lock); - -	dev->wd_timeout = timeout; -	wd_dev->timeout = timeout; -	mei_wd_set_start_timeout(dev, dev->wd_timeout); - -	mutex_unlock(&dev->device_lock); - -	return 0; -} - -/* - * Watchdog Device structs - */ -static const struct watchdog_ops wd_ops = { -		.owner = THIS_MODULE, -		.start = mei_wd_ops_start, -		.stop = mei_wd_ops_stop, -		.ping = mei_wd_ops_ping, -		.set_timeout = mei_wd_ops_set_timeout, -}; -static const struct watchdog_info wd_info = { -		.identity = INTEL_AMT_WATCHDOG_ID, -		.options = WDIOF_KEEPALIVEPING | -			   WDIOF_SETTIMEOUT | -			   WDIOF_ALARMONLY, -}; - -static struct watchdog_device amt_wd_dev = { -		.info = &wd_info, -		.ops = &wd_ops, -		.timeout = MEI_WD_DEFAULT_TIMEOUT, -		.min_timeout = MEI_WD_MIN_TIMEOUT, -		.max_timeout = MEI_WD_MAX_TIMEOUT, -}; - - -int mei_watchdog_register(struct mei_device *dev) -{ - -	int ret; - -	amt_wd_dev.parent = dev->dev; -	/* unlock to perserve correct locking order */ -	mutex_unlock(&dev->device_lock); -	ret = watchdog_register_device(&amt_wd_dev); -	mutex_lock(&dev->device_lock); -	if (ret) { -		dev_err(dev->dev, "wd: unable to register watchdog device = %d.\n", -			ret); -		return ret; -	} - -	dev_dbg(dev->dev, "wd: successfully register watchdog interface.\n"); -	watchdog_set_drvdata(&amt_wd_dev, dev); -	return 0; -} - -void mei_watchdog_unregister(struct mei_device *dev) -{ -	if (watchdog_get_drvdata(&amt_wd_dev) == NULL) -		return; - -	watchdog_set_drvdata(&amt_wd_dev, NULL); -	watchdog_unregister_device(&amt_wd_dev); -} - diff --git a/drivers/misc/mic/Kconfig b/drivers/misc/mic/Kconfig index 40677df7f996..2e4f3ba75c8e 100644 --- a/drivers/misc/mic/Kconfig +++ b/drivers/misc/mic/Kconfig @@ -32,12 +32,29 @@ config SCIF_BUS  	  OS and tools for MIC to use with this driver are available from  	  <http://software.intel.com/en-us/mic-developer>. +comment "VOP Bus Driver" + +config VOP_BUS +	tristate "VOP Bus Driver" +	depends on 64BIT && PCI && X86 && X86_DEV_DMA_OPS +	help +	  This option is selected by any driver which registers a +	  device or driver on the VOP Bus, such as CONFIG_INTEL_MIC_HOST +	  and CONFIG_INTEL_MIC_CARD. + +	  If you are building a host/card kernel with an Intel MIC device +	  then say M (recommended) or Y, else say N. If unsure say N. + +	  More information about the Intel MIC family as well as the Linux +	  OS and tools for MIC to use with this driver are available from +	  <http://software.intel.com/en-us/mic-developer>. +  comment "Intel MIC Host Driver"  config INTEL_MIC_HOST  	tristate "Intel MIC Host Driver" -	depends on 64BIT && PCI && X86 && INTEL_MIC_BUS && SCIF_BUS && MIC_COSM -	select VHOST_RING +	depends on 64BIT && PCI && X86 +	depends on INTEL_MIC_BUS && SCIF_BUS && MIC_COSM && VOP_BUS  	help  	  This enables Host Driver support for the Intel Many Integrated  	  Core (MIC) family of PCIe form factor coprocessor devices that @@ -56,7 +73,8 @@ comment "Intel MIC Card Driver"  config INTEL_MIC_CARD  	tristate "Intel MIC Card Driver" -	depends on 64BIT && X86 && INTEL_MIC_BUS && SCIF_BUS && MIC_COSM +	depends on 64BIT && X86 +	depends on INTEL_MIC_BUS && SCIF_BUS && MIC_COSM && VOP_BUS  	select VIRTIO  	help  	  This enables card driver support for the Intel Many Integrated @@ -107,3 +125,23 @@ config MIC_COSM  	  More information about the Intel MIC family as well as the Linux  	  OS and tools for MIC to use with this driver are available from  	  <http://software.intel.com/en-us/mic-developer>. + +comment "VOP Driver" + +config VOP +	tristate "VOP Driver" +	depends on 64BIT && PCI && X86 && VOP_BUS +	select VHOST_RING +	help +	  This enables VOP (Virtio over PCIe) Driver support for the Intel +	  Many Integrated Core (MIC) family of PCIe form factor coprocessor +	  devices. The VOP driver allows virtio drivers, e.g. net, console +	  and block drivers, on the card connect to user space virtio +	  devices on the host. + +	  If you are building a host kernel with an Intel MIC device then +	  say M (recommended) or Y, else say N. If unsure say N. + +	  More information about the Intel MIC family as well as the Linux +	  OS and tools for MIC to use with this driver are available from +	  <http://software.intel.com/en-us/mic-developer>. diff --git a/drivers/misc/mic/Makefile b/drivers/misc/mic/Makefile index e288a1106738..f2b1323ff96c 100644 --- a/drivers/misc/mic/Makefile +++ b/drivers/misc/mic/Makefile @@ -8,3 +8,4 @@ obj-y += bus/  obj-$(CONFIG_SCIF) += scif/  obj-$(CONFIG_MIC_COSM) += cosm/  obj-$(CONFIG_MIC_COSM) += cosm_client/ +obj-$(CONFIG_VOP) += vop/ diff --git a/drivers/misc/mic/bus/Makefile b/drivers/misc/mic/bus/Makefile index 761842b0d0bb..8758a7daa52c 100644 --- a/drivers/misc/mic/bus/Makefile +++ b/drivers/misc/mic/bus/Makefile @@ -5,3 +5,4 @@  obj-$(CONFIG_INTEL_MIC_BUS) += mic_bus.o  obj-$(CONFIG_SCIF_BUS) += scif_bus.o  obj-$(CONFIG_MIC_COSM) += cosm_bus.o +obj-$(CONFIG_VOP_BUS) += vop_bus.o diff --git a/drivers/misc/mic/bus/cosm_bus.h b/drivers/misc/mic/bus/cosm_bus.h index f7c57f266916..8b6341855dc3 100644 --- a/drivers/misc/mic/bus/cosm_bus.h +++ b/drivers/misc/mic/bus/cosm_bus.h @@ -30,6 +30,7 @@   * @attr_group: Pointer to list of sysfs attribute groups.   * @sdev: Device for sysfs entries.   * @state: MIC state. + * @prev_state: MIC state previous to MIC_RESETTING   * @shutdown_status: MIC status reported by card for shutdown/crashes.   * @shutdown_status_int: Internal shutdown status maintained by the driver   * @cosm_mutex: Mutex for synchronizing access to data structures. @@ -55,6 +56,7 @@ struct cosm_device {  	const struct attribute_group **attr_group;  	struct device *sdev;  	u8 state; +	u8 prev_state;  	u8 shutdown_status;  	u8 shutdown_status_int;  	struct mutex cosm_mutex; diff --git a/drivers/misc/mic/bus/vop_bus.c b/drivers/misc/mic/bus/vop_bus.c new file mode 100644 index 000000000000..303da222f5b6 --- /dev/null +++ b/drivers/misc/mic/bus/vop_bus.c @@ -0,0 +1,203 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel Virtio Over PCIe (VOP) Bus driver. + */ +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/idr.h> +#include <linux/dma-mapping.h> + +#include "vop_bus.h" + +static ssize_t device_show(struct device *d, +			   struct device_attribute *attr, char *buf) +{ +	struct vop_device *dev = dev_to_vop(d); + +	return sprintf(buf, "0x%04x\n", dev->id.device); +} +static DEVICE_ATTR_RO(device); + +static ssize_t vendor_show(struct device *d, +			   struct device_attribute *attr, char *buf) +{ +	struct vop_device *dev = dev_to_vop(d); + +	return sprintf(buf, "0x%04x\n", dev->id.vendor); +} +static DEVICE_ATTR_RO(vendor); + +static ssize_t modalias_show(struct device *d, +			     struct device_attribute *attr, char *buf) +{ +	struct vop_device *dev = dev_to_vop(d); + +	return sprintf(buf, "vop:d%08Xv%08X\n", +		       dev->id.device, dev->id.vendor); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *vop_dev_attrs[] = { +	&dev_attr_device.attr, +	&dev_attr_vendor.attr, +	&dev_attr_modalias.attr, +	NULL, +}; +ATTRIBUTE_GROUPS(vop_dev); + +static inline int vop_id_match(const struct vop_device *dev, +			       const struct vop_device_id *id) +{ +	if (id->device != dev->id.device && id->device != VOP_DEV_ANY_ID) +		return 0; + +	return id->vendor == VOP_DEV_ANY_ID || id->vendor == dev->id.vendor; +} + +/* + * This looks through all the IDs a driver claims to support.  If any of them + * match, we return 1 and the kernel will call vop_dev_probe(). + */ +static int vop_dev_match(struct device *dv, struct device_driver *dr) +{ +	unsigned int i; +	struct vop_device *dev = dev_to_vop(dv); +	const struct vop_device_id *ids; + +	ids = drv_to_vop(dr)->id_table; +	for (i = 0; ids[i].device; i++) +		if (vop_id_match(dev, &ids[i])) +			return 1; +	return 0; +} + +static int vop_uevent(struct device *dv, struct kobj_uevent_env *env) +{ +	struct vop_device *dev = dev_to_vop(dv); + +	return add_uevent_var(env, "MODALIAS=vop:d%08Xv%08X", +			      dev->id.device, dev->id.vendor); +} + +static int vop_dev_probe(struct device *d) +{ +	struct vop_device *dev = dev_to_vop(d); +	struct vop_driver *drv = drv_to_vop(dev->dev.driver); + +	return drv->probe(dev); +} + +static int vop_dev_remove(struct device *d) +{ +	struct vop_device *dev = dev_to_vop(d); +	struct vop_driver *drv = drv_to_vop(dev->dev.driver); + +	drv->remove(dev); +	return 0; +} + +static struct bus_type vop_bus = { +	.name  = "vop_bus", +	.match = vop_dev_match, +	.dev_groups = vop_dev_groups, +	.uevent = vop_uevent, +	.probe = vop_dev_probe, +	.remove = vop_dev_remove, +}; + +int vop_register_driver(struct vop_driver *driver) +{ +	driver->driver.bus = &vop_bus; +	return driver_register(&driver->driver); +} +EXPORT_SYMBOL_GPL(vop_register_driver); + +void vop_unregister_driver(struct vop_driver *driver) +{ +	driver_unregister(&driver->driver); +} +EXPORT_SYMBOL_GPL(vop_unregister_driver); + +static void vop_release_dev(struct device *d) +{ +	put_device(d); +} + +struct vop_device * +vop_register_device(struct device *pdev, int id, +		    const struct dma_map_ops *dma_ops, +		    struct vop_hw_ops *hw_ops, u8 dnode, struct mic_mw *aper, +		    struct dma_chan *chan) +{ +	int ret; +	struct vop_device *vdev; + +	vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); +	if (!vdev) +		return ERR_PTR(-ENOMEM); + +	vdev->dev.parent = pdev; +	vdev->id.device = id; +	vdev->id.vendor = VOP_DEV_ANY_ID; +	vdev->dev.archdata.dma_ops = (struct dma_map_ops *)dma_ops; +	vdev->dev.dma_mask = &vdev->dev.coherent_dma_mask; +	dma_set_mask(&vdev->dev, DMA_BIT_MASK(64)); +	vdev->dev.release = vop_release_dev; +	vdev->hw_ops = hw_ops; +	vdev->dev.bus = &vop_bus; +	vdev->dnode = dnode; +	vdev->aper = aper; +	vdev->dma_ch = chan; +	vdev->index = dnode - 1; +	dev_set_name(&vdev->dev, "vop-dev%u", vdev->index); +	/* +	 * device_register() causes the bus infrastructure to look for a +	 * matching driver. +	 */ +	ret = device_register(&vdev->dev); +	if (ret) +		goto free_vdev; +	return vdev; +free_vdev: +	kfree(vdev); +	return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(vop_register_device); + +void vop_unregister_device(struct vop_device *dev) +{ +	device_unregister(&dev->dev); +} +EXPORT_SYMBOL_GPL(vop_unregister_device); + +static int __init vop_init(void) +{ +	return bus_register(&vop_bus); +} + +static void __exit vop_exit(void) +{ +	bus_unregister(&vop_bus); +} + +core_initcall(vop_init); +module_exit(vop_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) VOP Bus driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mic/bus/vop_bus.h b/drivers/misc/mic/bus/vop_bus.h new file mode 100644 index 000000000000..fff7a865d721 --- /dev/null +++ b/drivers/misc/mic/bus/vop_bus.h @@ -0,0 +1,140 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel Virtio over PCIe Bus driver. + */ +#ifndef _VOP_BUS_H_ +#define _VOP_BUS_H_ +/* + * Everything a vop driver needs to work with any particular vop + * implementation. + */ +#include <linux/dmaengine.h> +#include <linux/interrupt.h> + +#include "../common/mic_dev.h" + +struct vop_device_id { +	u32 device; +	u32 vendor; +}; + +#define VOP_DEV_TRNSP 1 +#define VOP_DEV_ANY_ID 0xffffffff +/* + * Size of the internal buffer used during DMA's as an intermediate buffer + * for copy to/from user. Must be an integral number of pages. + */ +#define VOP_INT_DMA_BUF_SIZE PAGE_ALIGN(64 * 1024ULL) + +/** + * vop_device - representation of a device using vop + * @hw_ops: the hardware ops supported by this device. + * @id: the device type identification (used to match it with a driver). + * @dev: underlying device. + * @dnode - The destination node which this device will communicate with. + * @aper: Aperture memory window + * @dma_ch - DMA channel + * @index: unique position on the vop bus + */ +struct vop_device { +	struct vop_hw_ops *hw_ops; +	struct vop_device_id id; +	struct device dev; +	u8 dnode; +	struct mic_mw *aper; +	struct dma_chan *dma_ch; +	int index; +}; + +/** + * vop_driver - operations for a vop I/O driver + * @driver: underlying device driver (populate name and owner). + * @id_table: the ids serviced by this driver. + * @probe: the function to call when a device is found.  Returns 0 or -errno. + * @remove: the function to call when a device is removed. + */ +struct vop_driver { +	struct device_driver driver; +	const struct vop_device_id *id_table; +	int (*probe)(struct vop_device *dev); +	void (*remove)(struct vop_device *dev); +}; + +/** + * vop_hw_ops - Hardware operations for accessing a VOP device on the VOP bus. + * + * @next_db: Obtain the next available doorbell. + * @request_irq: Request an interrupt on a particular doorbell. + * @free_irq: Free an interrupt requested previously. + * @ack_interrupt: acknowledge an interrupt in the ISR. + * @get_remote_dp: Get access to the virtio device page used by the remote + *                 node to add/remove/configure virtio devices. + * @get_dp: Get access to the virtio device page used by the self + *          node to add/remove/configure virtio devices. + * @send_intr: Send an interrupt to the peer node on a specified doorbell. + * @ioremap: Map a buffer with the specified DMA address and length. + * @iounmap: Unmap a buffer previously mapped. + * @dma_filter: The DMA filter function to use for obtaining access to + *		a DMA channel on the peer node. + */ +struct vop_hw_ops { +	int (*next_db)(struct vop_device *vpdev); +	struct mic_irq *(*request_irq)(struct vop_device *vpdev, +				       irqreturn_t (*func)(int irq, void *data), +				       const char *name, void *data, +				       int intr_src); +	void (*free_irq)(struct vop_device *vpdev, +			 struct mic_irq *cookie, void *data); +	void (*ack_interrupt)(struct vop_device *vpdev, int num); +	void __iomem * (*get_remote_dp)(struct vop_device *vpdev); +	void * (*get_dp)(struct vop_device *vpdev); +	void (*send_intr)(struct vop_device *vpdev, int db); +	void __iomem * (*ioremap)(struct vop_device *vpdev, +				  dma_addr_t pa, size_t len); +	void (*iounmap)(struct vop_device *vpdev, void __iomem *va); +}; + +struct vop_device * +vop_register_device(struct device *pdev, int id, +		    const struct dma_map_ops *dma_ops, +		    struct vop_hw_ops *hw_ops, u8 dnode, struct mic_mw *aper, +		    struct dma_chan *chan); +void vop_unregister_device(struct vop_device *dev); +int vop_register_driver(struct vop_driver *drv); +void vop_unregister_driver(struct vop_driver *drv); + +/* + * module_vop_driver() - Helper macro for drivers that don't do + * anything special in module init/exit.  This eliminates a lot of + * boilerplate.  Each module may only use this macro once, and + * calling it replaces module_init() and module_exit() + */ +#define module_vop_driver(__vop_driver) \ +	module_driver(__vop_driver, vop_register_driver, \ +			vop_unregister_driver) + +static inline struct vop_device *dev_to_vop(struct device *dev) +{ +	return container_of(dev, struct vop_device, dev); +} + +static inline struct vop_driver *drv_to_vop(struct device_driver *drv) +{ +	return container_of(drv, struct vop_driver, driver); +} +#endif /* _VOP_BUS_H */ diff --git a/drivers/misc/mic/card/Makefile b/drivers/misc/mic/card/Makefile index 69d58bef92ce..6e9675e12a09 100644 --- a/drivers/misc/mic/card/Makefile +++ b/drivers/misc/mic/card/Makefile @@ -8,4 +8,3 @@ obj-$(CONFIG_INTEL_MIC_CARD) += mic_card.o  mic_card-y += mic_x100.o  mic_card-y += mic_device.o  mic_card-y += mic_debugfs.o -mic_card-y += mic_virtio.o diff --git a/drivers/misc/mic/card/mic_device.c b/drivers/misc/mic/card/mic_device.c index d0edaf7e0cd5..e749af48f736 100644 --- a/drivers/misc/mic/card/mic_device.c +++ b/drivers/misc/mic/card/mic_device.c @@ -34,7 +34,6 @@  #include <linux/mic_common.h>  #include "../common/mic_dev.h"  #include "mic_device.h" -#include "mic_virtio.h"  static struct mic_driver *g_drv; @@ -250,12 +249,82 @@ static struct scif_hw_ops scif_hw_ops = {  	.iounmap = ___mic_iounmap,  }; +static inline struct mic_driver *vpdev_to_mdrv(struct vop_device *vpdev) +{ +	return dev_get_drvdata(vpdev->dev.parent); +} + +static struct mic_irq * +__mic_request_irq(struct vop_device *vpdev, +		  irqreturn_t (*func)(int irq, void *data), +		   const char *name, void *data, int intr_src) +{ +	return mic_request_card_irq(func, NULL, name, data, intr_src); +} + +static void __mic_free_irq(struct vop_device *vpdev, +			   struct mic_irq *cookie, void *data) +{ +	return mic_free_card_irq(cookie, data); +} + +static void __mic_ack_interrupt(struct vop_device *vpdev, int num) +{ +	struct mic_driver *mdrv = vpdev_to_mdrv(vpdev); + +	mic_ack_interrupt(&mdrv->mdev); +} + +static int __mic_next_db(struct vop_device *vpdev) +{ +	return mic_next_card_db(); +} + +static void __iomem *__mic_get_remote_dp(struct vop_device *vpdev) +{ +	struct mic_driver *mdrv = vpdev_to_mdrv(vpdev); + +	return mdrv->dp; +} + +static void __mic_send_intr(struct vop_device *vpdev, int db) +{ +	struct mic_driver *mdrv = vpdev_to_mdrv(vpdev); + +	mic_send_intr(&mdrv->mdev, db); +} + +static void __iomem *__mic_ioremap(struct vop_device *vpdev, +				   dma_addr_t pa, size_t len) +{ +	struct mic_driver *mdrv = vpdev_to_mdrv(vpdev); + +	return mic_card_map(&mdrv->mdev, pa, len); +} + +static void __mic_iounmap(struct vop_device *vpdev, void __iomem *va) +{ +	struct mic_driver *mdrv = vpdev_to_mdrv(vpdev); + +	mic_card_unmap(&mdrv->mdev, va); +} + +static struct vop_hw_ops vop_hw_ops = { +	.request_irq = __mic_request_irq, +	.free_irq = __mic_free_irq, +	.ack_interrupt = __mic_ack_interrupt, +	.next_db = __mic_next_db, +	.get_remote_dp = __mic_get_remote_dp, +	.send_intr = __mic_send_intr, +	.ioremap = __mic_ioremap, +	.iounmap = __mic_iounmap, +}; +  static int mic_request_dma_chans(struct mic_driver *mdrv)  {  	dma_cap_mask_t mask;  	struct dma_chan *chan; -	request_module("mic_x100_dma");  	dma_cap_zero(mask);  	dma_cap_set(DMA_MEMCPY, mask); @@ -309,9 +378,13 @@ int __init mic_driver_init(struct mic_driver *mdrv)  		rc = -ENODEV;  		goto irq_uninit;  	} -	rc = mic_devices_init(mdrv); -	if (rc) +	mdrv->vpdev = vop_register_device(mdrv->dev, VOP_DEV_TRNSP, +					  NULL, &vop_hw_ops, 0, +					  NULL, mdrv->dma_ch[0]); +	if (IS_ERR(mdrv->vpdev)) { +		rc = PTR_ERR(mdrv->vpdev);  		goto dma_free; +	}  	bootparam = mdrv->dp;  	node_id = ioread8(&bootparam->node_id);  	mdrv->scdev = scif_register_device(mdrv->dev, MIC_SCIF_DEV, @@ -321,13 +394,13 @@ int __init mic_driver_init(struct mic_driver *mdrv)  					   mdrv->num_dma_ch, true);  	if (IS_ERR(mdrv->scdev)) {  		rc = PTR_ERR(mdrv->scdev); -		goto device_uninit; +		goto vop_remove;  	}  	mic_create_card_debug_dir(mdrv);  done:  	return rc; -device_uninit: -	mic_devices_uninit(mdrv); +vop_remove: +	vop_unregister_device(mdrv->vpdev);  dma_free:  	mic_free_dma_chans(mdrv);  irq_uninit: @@ -348,7 +421,7 @@ void mic_driver_uninit(struct mic_driver *mdrv)  {  	mic_delete_card_debug_dir(mdrv);  	scif_unregister_device(mdrv->scdev); -	mic_devices_uninit(mdrv); +	vop_unregister_device(mdrv->vpdev);  	mic_free_dma_chans(mdrv);  	mic_uninit_irq();  	mic_dp_uninit(); diff --git a/drivers/misc/mic/card/mic_device.h b/drivers/misc/mic/card/mic_device.h index 1dbf83c41289..333dbed972f6 100644 --- a/drivers/misc/mic/card/mic_device.h +++ b/drivers/misc/mic/card/mic_device.h @@ -32,6 +32,7 @@  #include <linux/interrupt.h>  #include <linux/mic_bus.h>  #include "../bus/scif_bus.h" +#include "../bus/vop_bus.h"  /**   * struct mic_intr_info - Contains h/w specific interrupt sources info @@ -76,6 +77,7 @@ struct mic_device {   * @dma_ch - Array of DMA channels   * @num_dma_ch - Number of DMA channels available   * @scdev: SCIF device on the SCIF virtual bus. + * @vpdev: Virtio over PCIe device on the VOP virtual bus.   */  struct mic_driver {  	char name[20]; @@ -90,6 +92,7 @@ struct mic_driver {  	struct dma_chan *dma_ch[MIC_MAX_DMA_CHAN];  	int num_dma_ch;  	struct scif_hw_dev *scdev; +	struct vop_device *vpdev;  };  /** diff --git a/drivers/misc/mic/card/mic_virtio.c b/drivers/misc/mic/card/mic_virtio.c deleted file mode 100644 index f6ed57d3125c..000000000000 --- a/drivers/misc/mic/card/mic_virtio.c +++ /dev/null @@ -1,634 +0,0 @@ -/* - * Intel MIC Platform Software Stack (MPSS) - * - * Copyright(c) 2013 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * The full GNU General Public License is included in this distribution in - * the file called "COPYING". - * - * Disclaimer: The codes contained in these modules may be specific to - * the Intel Software Development Platform codenamed: Knights Ferry, and - * the Intel product codenamed: Knights Corner, and are not backward - * compatible with other Intel products. Additionally, Intel will NOT - * support the codes or instruction set in future products. - * - * Adapted from: - * - * virtio for kvm on s390 - * - * Copyright IBM Corp. 2008 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License (version 2 only) - * as published by the Free Software Foundation. - * - *    Author(s): Christian Borntraeger <borntraeger@de.ibm.com> - * - * Intel MIC Card driver. - * - */ -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/virtio_config.h> - -#include "../common/mic_dev.h" -#include "mic_virtio.h" - -#define VIRTIO_SUBCODE_64 0x0D00 - -#define MIC_MAX_VRINGS                4 -struct mic_vdev { -	struct virtio_device vdev; -	struct mic_device_desc __iomem *desc; -	struct mic_device_ctrl __iomem *dc; -	struct mic_device *mdev; -	void __iomem *vr[MIC_MAX_VRINGS]; -	int used_size[MIC_MAX_VRINGS]; -	struct completion reset_done; -	struct mic_irq *virtio_cookie; -	int c2h_vdev_db; -}; - -static struct mic_irq *virtio_config_cookie; -#define to_micvdev(vd) container_of(vd, struct mic_vdev, vdev) - -/* Helper API to obtain the parent of the virtio device */ -static inline struct device *mic_dev(struct mic_vdev *mvdev) -{ -	return mvdev->vdev.dev.parent; -} - -/* This gets the device's feature bits. */ -static u64 mic_get_features(struct virtio_device *vdev) -{ -	unsigned int i, bits; -	u32 features = 0; -	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; -	u8 __iomem *in_features = mic_vq_features(desc); -	int feature_len = ioread8(&desc->feature_len); - -	bits = min_t(unsigned, feature_len, sizeof(features)) * 8; -	for (i = 0; i < bits; i++) -		if (ioread8(&in_features[i / 8]) & (BIT(i % 8))) -			features |= BIT(i); - -	return features; -} - -static int mic_finalize_features(struct virtio_device *vdev) -{ -	unsigned int i, bits; -	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; -	u8 feature_len = ioread8(&desc->feature_len); -	/* Second half of bitmap is features we accept. */ -	u8 __iomem *out_features = -		mic_vq_features(desc) + feature_len; - -	/* Give virtio_ring a chance to accept features. */ -	vring_transport_features(vdev); - -	/* Make sure we don't have any features > 32 bits! */ -	BUG_ON((u32)vdev->features != vdev->features); - -	memset_io(out_features, 0, feature_len); -	bits = min_t(unsigned, feature_len, -		sizeof(vdev->features)) * 8; -	for (i = 0; i < bits; i++) { -		if (__virtio_test_bit(vdev, i)) -			iowrite8(ioread8(&out_features[i / 8]) | (1 << (i % 8)), -				 &out_features[i / 8]); -	} - -	return 0; -} - -/* - * Reading and writing elements in config space - */ -static void mic_get(struct virtio_device *vdev, unsigned int offset, -		   void *buf, unsigned len) -{ -	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; - -	if (offset + len > ioread8(&desc->config_len)) -		return; -	memcpy_fromio(buf, mic_vq_configspace(desc) + offset, len); -} - -static void mic_set(struct virtio_device *vdev, unsigned int offset, -		   const void *buf, unsigned len) -{ -	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; - -	if (offset + len > ioread8(&desc->config_len)) -		return; -	memcpy_toio(mic_vq_configspace(desc) + offset, buf, len); -} - -/* - * The operations to get and set the status word just access the status - * field of the device descriptor. set_status also interrupts the host - * to tell about status changes. - */ -static u8 mic_get_status(struct virtio_device *vdev) -{ -	return ioread8(&to_micvdev(vdev)->desc->status); -} - -static void mic_set_status(struct virtio_device *vdev, u8 status) -{ -	struct mic_vdev *mvdev = to_micvdev(vdev); -	if (!status) -		return; -	iowrite8(status, &mvdev->desc->status); -	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); -} - -/* Inform host on a virtio device reset and wait for ack from host */ -static void mic_reset_inform_host(struct virtio_device *vdev) -{ -	struct mic_vdev *mvdev = to_micvdev(vdev); -	struct mic_device_ctrl __iomem *dc = mvdev->dc; -	int retry; - -	iowrite8(0, &dc->host_ack); -	iowrite8(1, &dc->vdev_reset); -	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); - -	/* Wait till host completes all card accesses and acks the reset */ -	for (retry = 100; retry--;) { -		if (ioread8(&dc->host_ack)) -			break; -		msleep(100); -	}; - -	dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry); - -	/* Reset status to 0 in case we timed out */ -	iowrite8(0, &mvdev->desc->status); -} - -static void mic_reset(struct virtio_device *vdev) -{ -	struct mic_vdev *mvdev = to_micvdev(vdev); - -	dev_dbg(mic_dev(mvdev), "%s: virtio id %d\n", -		__func__, vdev->id.device); - -	mic_reset_inform_host(vdev); -	complete_all(&mvdev->reset_done); -} - -/* - * The virtio_ring code calls this API when it wants to notify the Host. - */ -static bool mic_notify(struct virtqueue *vq) -{ -	struct mic_vdev *mvdev = vq->priv; - -	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); -	return true; -} - -static void mic_del_vq(struct virtqueue *vq, int n) -{ -	struct mic_vdev *mvdev = to_micvdev(vq->vdev); -	struct vring *vr = (struct vring *)(vq + 1); - -	free_pages((unsigned long) vr->used, get_order(mvdev->used_size[n])); -	vring_del_virtqueue(vq); -	mic_card_unmap(mvdev->mdev, mvdev->vr[n]); -	mvdev->vr[n] = NULL; -} - -static void mic_del_vqs(struct virtio_device *vdev) -{ -	struct mic_vdev *mvdev = to_micvdev(vdev); -	struct virtqueue *vq, *n; -	int idx = 0; - -	dev_dbg(mic_dev(mvdev), "%s\n", __func__); - -	list_for_each_entry_safe(vq, n, &vdev->vqs, list) -		mic_del_vq(vq, idx++); -} - -/* - * This routine will assign vring's allocated in host/io memory. Code in - * virtio_ring.c however continues to access this io memory as if it were local - * memory without io accessors. - */ -static struct virtqueue *mic_find_vq(struct virtio_device *vdev, -				     unsigned index, -				     void (*callback)(struct virtqueue *vq), -				     const char *name) -{ -	struct mic_vdev *mvdev = to_micvdev(vdev); -	struct mic_vqconfig __iomem *vqconfig; -	struct mic_vqconfig config; -	struct virtqueue *vq; -	void __iomem *va; -	struct _mic_vring_info __iomem *info; -	void *used; -	int vr_size, _vr_size, err, magic; -	struct vring *vr; -	u8 type = ioread8(&mvdev->desc->type); - -	if (index >= ioread8(&mvdev->desc->num_vq)) -		return ERR_PTR(-ENOENT); - -	if (!name) -		return ERR_PTR(-ENOENT); - -	/* First assign the vring's allocated in host memory */ -	vqconfig = mic_vq_config(mvdev->desc) + index; -	memcpy_fromio(&config, vqconfig, sizeof(config)); -	_vr_size = vring_size(le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN); -	vr_size = PAGE_ALIGN(_vr_size + sizeof(struct _mic_vring_info)); -	va = mic_card_map(mvdev->mdev, le64_to_cpu(config.address), vr_size); -	if (!va) -		return ERR_PTR(-ENOMEM); -	mvdev->vr[index] = va; -	memset_io(va, 0x0, _vr_size); -	vq = vring_new_virtqueue(index, le16_to_cpu(config.num), -				 MIC_VIRTIO_RING_ALIGN, vdev, false, -				 (void __force *)va, mic_notify, callback, -				 name); -	if (!vq) { -		err = -ENOMEM; -		goto unmap; -	} -	info = va + _vr_size; -	magic = ioread32(&info->magic); - -	if (WARN(magic != MIC_MAGIC + type + index, "magic mismatch")) { -		err = -EIO; -		goto unmap; -	} - -	/* Allocate and reassign used ring now */ -	mvdev->used_size[index] = PAGE_ALIGN(sizeof(__u16) * 3 + -					     sizeof(struct vring_used_elem) * -					     le16_to_cpu(config.num)); -	used = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, -					get_order(mvdev->used_size[index])); -	if (!used) { -		err = -ENOMEM; -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, err); -		goto del_vq; -	} -	iowrite64(virt_to_phys(used), &vqconfig->used_address); - -	/* -	 * To reassign the used ring here we are directly accessing -	 * struct vring_virtqueue which is a private data structure -	 * in virtio_ring.c. At the minimum, a BUILD_BUG_ON() in -	 * vring_new_virtqueue() would ensure that -	 *  (&vq->vring == (struct vring *) (&vq->vq + 1)); -	 */ -	vr = (struct vring *)(vq + 1); -	vr->used = used; - -	vq->priv = mvdev; -	return vq; -del_vq: -	vring_del_virtqueue(vq); -unmap: -	mic_card_unmap(mvdev->mdev, mvdev->vr[index]); -	return ERR_PTR(err); -} - -static int mic_find_vqs(struct virtio_device *vdev, unsigned nvqs, -			struct virtqueue *vqs[], -			vq_callback_t *callbacks[], -			const char * const names[]) -{ -	struct mic_vdev *mvdev = to_micvdev(vdev); -	struct mic_device_ctrl __iomem *dc = mvdev->dc; -	int i, err, retry; - -	/* We must have this many virtqueues. */ -	if (nvqs > ioread8(&mvdev->desc->num_vq)) -		return -ENOENT; - -	for (i = 0; i < nvqs; ++i) { -		dev_dbg(mic_dev(mvdev), "%s: %d: %s\n", -			__func__, i, names[i]); -		vqs[i] = mic_find_vq(vdev, i, callbacks[i], names[i]); -		if (IS_ERR(vqs[i])) { -			err = PTR_ERR(vqs[i]); -			goto error; -		} -	} - -	iowrite8(1, &dc->used_address_updated); -	/* -	 * Send an interrupt to the host to inform it that used -	 * rings have been re-assigned. -	 */ -	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); -	for (retry = 100; retry--;) { -		if (!ioread8(&dc->used_address_updated)) -			break; -		msleep(100); -	}; - -	dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry); -	if (!retry) { -		err = -ENODEV; -		goto error; -	} - -	return 0; -error: -	mic_del_vqs(vdev); -	return err; -} - -/* - * The config ops structure as defined by virtio config - */ -static struct virtio_config_ops mic_vq_config_ops = { -	.get_features = mic_get_features, -	.finalize_features = mic_finalize_features, -	.get = mic_get, -	.set = mic_set, -	.get_status = mic_get_status, -	.set_status = mic_set_status, -	.reset = mic_reset, -	.find_vqs = mic_find_vqs, -	.del_vqs = mic_del_vqs, -}; - -static irqreturn_t -mic_virtio_intr_handler(int irq, void *data) -{ -	struct mic_vdev *mvdev = data; -	struct virtqueue *vq; - -	mic_ack_interrupt(mvdev->mdev); -	list_for_each_entry(vq, &mvdev->vdev.vqs, list) -		vring_interrupt(0, vq); - -	return IRQ_HANDLED; -} - -static void mic_virtio_release_dev(struct device *_d) -{ -	/* -	 * No need for a release method similar to virtio PCI. -	 * Provide an empty one to avoid getting a warning from core. -	 */ -} - -/* - * adds a new device and register it with virtio - * appropriate drivers are loaded by the device model - */ -static int mic_add_device(struct mic_device_desc __iomem *d, -	unsigned int offset, struct mic_driver *mdrv) -{ -	struct mic_vdev *mvdev; -	int ret; -	int virtio_db; -	u8 type = ioread8(&d->type); - -	mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL); -	if (!mvdev) { -		dev_err(mdrv->dev, "Cannot allocate mic dev %u type %u\n", -			offset, type); -		return -ENOMEM; -	} - -	mvdev->mdev = &mdrv->mdev; -	mvdev->vdev.dev.parent = mdrv->dev; -	mvdev->vdev.dev.release = mic_virtio_release_dev; -	mvdev->vdev.id.device = type; -	mvdev->vdev.config = &mic_vq_config_ops; -	mvdev->desc = d; -	mvdev->dc = (void __iomem *)d + mic_aligned_desc_size(d); -	init_completion(&mvdev->reset_done); - -	virtio_db = mic_next_card_db(); -	mvdev->virtio_cookie = mic_request_card_irq(mic_virtio_intr_handler, -			NULL, "virtio intr", mvdev, virtio_db); -	if (IS_ERR(mvdev->virtio_cookie)) { -		ret = PTR_ERR(mvdev->virtio_cookie); -		goto kfree; -	} -	iowrite8((u8)virtio_db, &mvdev->dc->h2c_vdev_db); -	mvdev->c2h_vdev_db = ioread8(&mvdev->dc->c2h_vdev_db); - -	ret = register_virtio_device(&mvdev->vdev); -	if (ret) { -		dev_err(mic_dev(mvdev), -			"Failed to register mic device %u type %u\n", -			offset, type); -		goto free_irq; -	} -	iowrite64((u64)mvdev, &mvdev->dc->vdev); -	dev_dbg(mic_dev(mvdev), "%s: registered mic device %u type %u mvdev %p\n", -		__func__, offset, type, mvdev); - -	return 0; - -free_irq: -	mic_free_card_irq(mvdev->virtio_cookie, mvdev); -kfree: -	kfree(mvdev); -	return ret; -} - -/* - * match for a mic device with a specific desc pointer - */ -static int mic_match_desc(struct device *dev, void *data) -{ -	struct virtio_device *vdev = dev_to_virtio(dev); -	struct mic_vdev *mvdev = to_micvdev(vdev); - -	return mvdev->desc == (void __iomem *)data; -} - -static void mic_handle_config_change(struct mic_device_desc __iomem *d, -	unsigned int offset, struct mic_driver *mdrv) -{ -	struct mic_device_ctrl __iomem *dc -		= (void __iomem *)d + mic_aligned_desc_size(d); -	struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev); - -	if (ioread8(&dc->config_change) != MIC_VIRTIO_PARAM_CONFIG_CHANGED) -		return; - -	dev_dbg(mdrv->dev, "%s %d\n", __func__, __LINE__); -	virtio_config_changed(&mvdev->vdev); -	iowrite8(1, &dc->guest_ack); -} - -/* - * removes a virtio device if a hot remove event has been - * requested by the host. - */ -static int mic_remove_device(struct mic_device_desc __iomem *d, -	unsigned int offset, struct mic_driver *mdrv) -{ -	struct mic_device_ctrl __iomem *dc -		= (void __iomem *)d + mic_aligned_desc_size(d); -	struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev); -	u8 status; -	int ret = -1; - -	if (ioread8(&dc->config_change) == MIC_VIRTIO_PARAM_DEV_REMOVE) { -		dev_dbg(mdrv->dev, -			"%s %d config_change %d type %d mvdev %p\n", -			__func__, __LINE__, -			ioread8(&dc->config_change), ioread8(&d->type), mvdev); - -		status = ioread8(&d->status); -		reinit_completion(&mvdev->reset_done); -		unregister_virtio_device(&mvdev->vdev); -		mic_free_card_irq(mvdev->virtio_cookie, mvdev); -		if (status & VIRTIO_CONFIG_S_DRIVER_OK) -			wait_for_completion(&mvdev->reset_done); -		kfree(mvdev); -		iowrite8(1, &dc->guest_ack); -		dev_dbg(mdrv->dev, "%s %d guest_ack %d\n", -			__func__, __LINE__, ioread8(&dc->guest_ack)); -		ret = 0; -	} - -	return ret; -} - -#define REMOVE_DEVICES true - -static void mic_scan_devices(struct mic_driver *mdrv, bool remove) -{ -	s8 type; -	unsigned int i; -	struct mic_device_desc __iomem *d; -	struct mic_device_ctrl __iomem *dc; -	struct device *dev; -	int ret; - -	for (i = sizeof(struct mic_bootparam); i < MIC_DP_SIZE; -		i += mic_total_desc_size(d)) { -		d = mdrv->dp + i; -		dc = (void __iomem *)d + mic_aligned_desc_size(d); -		/* -		 * This read barrier is paired with the corresponding write -		 * barrier on the host which is inserted before adding or -		 * removing a virtio device descriptor, by updating the type. -		 */ -		rmb(); -		type = ioread8(&d->type); - -		/* end of list */ -		if (type == 0) -			break; - -		if (type == -1) -			continue; - -		/* device already exists */ -		dev = device_find_child(mdrv->dev, (void __force *)d, -					mic_match_desc); -		if (dev) { -			if (remove) -				iowrite8(MIC_VIRTIO_PARAM_DEV_REMOVE, -					 &dc->config_change); -			put_device(dev); -			mic_handle_config_change(d, i, mdrv); -			ret = mic_remove_device(d, i, mdrv); -			if (!ret && !remove) -				iowrite8(-1, &d->type); -			if (remove) { -				iowrite8(0, &dc->config_change); -				iowrite8(0, &dc->guest_ack); -			} -			continue; -		} - -		/* new device */ -		dev_dbg(mdrv->dev, "%s %d Adding new virtio device %p\n", -			__func__, __LINE__, d); -		if (!remove) -			mic_add_device(d, i, mdrv); -	} -} - -/* - * mic_hotplug_device tries to find changes in the device page. - */ -static void mic_hotplug_devices(struct work_struct *work) -{ -	struct mic_driver *mdrv = container_of(work, -		struct mic_driver, hotplug_work); - -	mic_scan_devices(mdrv, !REMOVE_DEVICES); -} - -/* - * Interrupt handler for hot plug/config changes etc. - */ -static irqreturn_t -mic_extint_handler(int irq, void *data) -{ -	struct mic_driver *mdrv = (struct mic_driver *)data; - -	dev_dbg(mdrv->dev, "%s %d hotplug work\n", -		__func__, __LINE__); -	mic_ack_interrupt(&mdrv->mdev); -	schedule_work(&mdrv->hotplug_work); -	return IRQ_HANDLED; -} - -/* - * Init function for virtio - */ -int mic_devices_init(struct mic_driver *mdrv) -{ -	int rc; -	struct mic_bootparam __iomem *bootparam; -	int config_db; - -	INIT_WORK(&mdrv->hotplug_work, mic_hotplug_devices); -	mic_scan_devices(mdrv, !REMOVE_DEVICES); - -	config_db = mic_next_card_db(); -	virtio_config_cookie = mic_request_card_irq(mic_extint_handler, NULL, -						    "virtio_config_intr", mdrv, -						    config_db); -	if (IS_ERR(virtio_config_cookie)) { -		rc = PTR_ERR(virtio_config_cookie); -		goto exit; -	} - -	bootparam = mdrv->dp; -	iowrite8(config_db, &bootparam->h2c_config_db); -	return 0; -exit: -	return rc; -} - -/* - * Uninit function for virtio - */ -void mic_devices_uninit(struct mic_driver *mdrv) -{ -	struct mic_bootparam __iomem *bootparam = mdrv->dp; -	iowrite8(-1, &bootparam->h2c_config_db); -	mic_free_card_irq(virtio_config_cookie, mdrv); -	flush_work(&mdrv->hotplug_work); -	mic_scan_devices(mdrv, REMOVE_DEVICES); -} diff --git a/drivers/misc/mic/card/mic_virtio.h b/drivers/misc/mic/card/mic_virtio.h deleted file mode 100644 index d0407ba53bb7..000000000000 --- a/drivers/misc/mic/card/mic_virtio.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Intel MIC Platform Software Stack (MPSS) - * - * Copyright(c) 2013 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * The full GNU General Public License is included in this distribution in - * the file called "COPYING". - * - * Disclaimer: The codes contained in these modules may be specific to - * the Intel Software Development Platform codenamed: Knights Ferry, and - * the Intel product codenamed: Knights Corner, and are not backward - * compatible with other Intel products. Additionally, Intel will NOT - * support the codes or instruction set in future products. - * - * Intel MIC Card driver. - * - */ -#ifndef __MIC_CARD_VIRTIO_H -#define __MIC_CARD_VIRTIO_H - -#include <linux/mic_common.h> -#include "mic_device.h" - -/* - * 64 bit I/O access - */ -#ifndef ioread64 -#define ioread64 readq -#endif -#ifndef iowrite64 -#define iowrite64 writeq -#endif - -static inline unsigned mic_desc_size(struct mic_device_desc __iomem *desc) -{ -	return sizeof(*desc) -		+ ioread8(&desc->num_vq) * sizeof(struct mic_vqconfig) -		+ ioread8(&desc->feature_len) * 2 -		+ ioread8(&desc->config_len); -} - -static inline struct mic_vqconfig __iomem * -mic_vq_config(struct mic_device_desc __iomem *desc) -{ -	return (struct mic_vqconfig __iomem *)(desc + 1); -} - -static inline __u8 __iomem * -mic_vq_features(struct mic_device_desc __iomem *desc) -{ -	return (__u8 __iomem *)(mic_vq_config(desc) + ioread8(&desc->num_vq)); -} - -static inline __u8 __iomem * -mic_vq_configspace(struct mic_device_desc __iomem *desc) -{ -	return mic_vq_features(desc) + ioread8(&desc->feature_len) * 2; -} -static inline unsigned mic_total_desc_size(struct mic_device_desc __iomem *desc) -{ -	return mic_aligned_desc_size(desc) + sizeof(struct mic_device_ctrl); -} - -int mic_devices_init(struct mic_driver *mdrv); -void mic_devices_uninit(struct mic_driver *mdrv); - -#endif diff --git a/drivers/misc/mic/card/mic_x100.c b/drivers/misc/mic/card/mic_x100.c index b2958ce2368c..b9f0710ffa6b 100644 --- a/drivers/misc/mic/card/mic_x100.c +++ b/drivers/misc/mic/card/mic_x100.c @@ -326,6 +326,7 @@ static int __init mic_init(void)  		goto done;  	} +	request_module("mic_x100_dma");  	mic_init_card_debugfs();  	ret = platform_device_register(&mic_platform_dev);  	if (ret) { diff --git a/drivers/misc/mic/cosm/cosm_main.c b/drivers/misc/mic/cosm/cosm_main.c index 4b4b356c797d..7005cb1e01d2 100644 --- a/drivers/misc/mic/cosm/cosm_main.c +++ b/drivers/misc/mic/cosm/cosm_main.c @@ -153,8 +153,10 @@ void cosm_stop(struct cosm_device *cdev, bool force)  		 * stop(..) calls device_unregister and will crash the system if  		 * called multiple times.  		 */ -		bool call_hw_ops = cdev->state != MIC_RESET_FAILED && -					cdev->state != MIC_READY; +		u8 state = cdev->state == MIC_RESETTING ? +					cdev->prev_state : cdev->state; +		bool call_hw_ops = state != MIC_RESET_FAILED && +					state != MIC_READY;  		if (cdev->state != MIC_RESETTING)  			cosm_set_state(cdev, MIC_RESETTING); @@ -195,8 +197,11 @@ int cosm_reset(struct cosm_device *cdev)  	mutex_lock(&cdev->cosm_mutex);  	if (cdev->state != MIC_READY) { -		cosm_set_state(cdev, MIC_RESETTING); -		schedule_work(&cdev->reset_trigger_work); +		if (cdev->state != MIC_RESETTING) { +			cdev->prev_state = cdev->state; +			cosm_set_state(cdev, MIC_RESETTING); +			schedule_work(&cdev->reset_trigger_work); +		}  	} else {  		dev_err(&cdev->dev, "%s %d MIC is READY\n", __func__, __LINE__);  		rc = -EINVAL; diff --git a/drivers/misc/mic/host/Makefile b/drivers/misc/mic/host/Makefile index 004d3db0f990..f3b502333ded 100644 --- a/drivers/misc/mic/host/Makefile +++ b/drivers/misc/mic/host/Makefile @@ -9,5 +9,3 @@ mic_host-objs += mic_smpt.o  mic_host-objs += mic_intr.o  mic_host-objs += mic_boot.o  mic_host-objs += mic_debugfs.o -mic_host-objs += mic_fops.o -mic_host-objs += mic_virtio.o diff --git a/drivers/misc/mic/host/mic_boot.c b/drivers/misc/mic/host/mic_boot.c index 7845564dff64..8c91c9950b54 100644 --- a/drivers/misc/mic/host/mic_boot.c +++ b/drivers/misc/mic/host/mic_boot.c @@ -25,10 +25,117 @@  #include <linux/mic_common.h>  #include <linux/mic_bus.h>  #include "../bus/scif_bus.h" +#include "../bus/vop_bus.h"  #include "../common/mic_dev.h"  #include "mic_device.h"  #include "mic_smpt.h" -#include "mic_virtio.h" + +static inline struct mic_device *vpdev_to_mdev(struct device *dev) +{ +	return dev_get_drvdata(dev->parent); +} + +static dma_addr_t +_mic_dma_map_page(struct device *dev, struct page *page, +		  unsigned long offset, size_t size, +		  enum dma_data_direction dir, struct dma_attrs *attrs) +{ +	void *va = phys_to_virt(page_to_phys(page)) + offset; +	struct mic_device *mdev = vpdev_to_mdev(dev); + +	return mic_map_single(mdev, va, size); +} + +static void _mic_dma_unmap_page(struct device *dev, dma_addr_t dma_addr, +				size_t size, enum dma_data_direction dir, +				struct dma_attrs *attrs) +{ +	struct mic_device *mdev = vpdev_to_mdev(dev); + +	mic_unmap_single(mdev, dma_addr, size); +} + +static const struct dma_map_ops _mic_dma_ops = { +	.map_page = _mic_dma_map_page, +	.unmap_page = _mic_dma_unmap_page, +}; + +static struct mic_irq * +__mic_request_irq(struct vop_device *vpdev, +		  irqreturn_t (*func)(int irq, void *data), +		  const char *name, void *data, int intr_src) +{ +	struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev); + +	return mic_request_threaded_irq(mdev, func, NULL, name, data, +					intr_src, MIC_INTR_DB); +} + +static void __mic_free_irq(struct vop_device *vpdev, +			   struct mic_irq *cookie, void *data) +{ +	struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev); + +	return mic_free_irq(mdev, cookie, data); +} + +static void __mic_ack_interrupt(struct vop_device *vpdev, int num) +{ +	struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev); + +	mdev->ops->intr_workarounds(mdev); +} + +static int __mic_next_db(struct vop_device *vpdev) +{ +	struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev); + +	return mic_next_db(mdev); +} + +static void *__mic_get_dp(struct vop_device *vpdev) +{ +	struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev); + +	return mdev->dp; +} + +static void __iomem *__mic_get_remote_dp(struct vop_device *vpdev) +{ +	return NULL; +} + +static void __mic_send_intr(struct vop_device *vpdev, int db) +{ +	struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev); + +	mdev->ops->send_intr(mdev, db); +} + +static void __iomem *__mic_ioremap(struct vop_device *vpdev, +				   dma_addr_t pa, size_t len) +{ +	struct mic_device *mdev = vpdev_to_mdev(&vpdev->dev); + +	return mdev->aper.va + pa; +} + +static void __mic_iounmap(struct vop_device *vpdev, void __iomem *va) +{ +	/* nothing to do */ +} + +static struct vop_hw_ops vop_hw_ops = { +	.request_irq = __mic_request_irq, +	.free_irq = __mic_free_irq, +	.ack_interrupt = __mic_ack_interrupt, +	.next_db = __mic_next_db, +	.get_dp = __mic_get_dp, +	.get_remote_dp = __mic_get_remote_dp, +	.send_intr = __mic_send_intr, +	.ioremap = __mic_ioremap, +	.iounmap = __mic_iounmap, +};  static inline struct mic_device *scdev_to_mdev(struct scif_hw_dev *scdev)  { @@ -315,7 +422,6 @@ static int mic_request_dma_chans(struct mic_device *mdev)  	dma_cap_mask_t mask;  	struct dma_chan *chan; -	request_module("mic_x100_dma");  	dma_cap_zero(mask);  	dma_cap_set(DMA_MEMCPY, mask); @@ -387,9 +493,18 @@ static int _mic_start(struct cosm_device *cdev, int id)  		goto dma_free;  	} +	mdev->vpdev = vop_register_device(&mdev->pdev->dev, +					  VOP_DEV_TRNSP, &_mic_dma_ops, +					  &vop_hw_ops, id + 1, &mdev->aper, +					  mdev->dma_ch[0]); +	if (IS_ERR(mdev->vpdev)) { +		rc = PTR_ERR(mdev->vpdev); +		goto scif_remove; +	} +  	rc = mdev->ops->load_mic_fw(mdev, NULL);  	if (rc) -		goto scif_remove; +		goto vop_remove;  	mic_smpt_restore(mdev);  	mic_intr_restore(mdev);  	mdev->intr_ops->enable_interrupts(mdev); @@ -397,6 +512,8 @@ static int _mic_start(struct cosm_device *cdev, int id)  	mdev->ops->write_spad(mdev, MIC_DPHI_SPAD, mdev->dp_dma_addr >> 32);  	mdev->ops->send_firmware_intr(mdev);  	goto unlock_ret; +vop_remove: +	vop_unregister_device(mdev->vpdev);  scif_remove:  	scif_unregister_device(mdev->scdev);  dma_free: @@ -423,7 +540,7 @@ static void _mic_stop(struct cosm_device *cdev, bool force)  	 * will be the first to be registered and the last to be  	 * unregistered.  	 */ -	mic_virtio_reset_devices(mdev); +	vop_unregister_device(mdev->vpdev);  	scif_unregister_device(mdev->scdev);  	mic_free_dma_chans(mdev);  	mbus_unregister_device(mdev->dma_mbdev); diff --git a/drivers/misc/mic/host/mic_debugfs.c b/drivers/misc/mic/host/mic_debugfs.c index 10581600777a..0a9daba8bb5d 100644 --- a/drivers/misc/mic/host/mic_debugfs.c +++ b/drivers/misc/mic/host/mic_debugfs.c @@ -26,7 +26,6 @@  #include "../common/mic_dev.h"  #include "mic_device.h"  #include "mic_smpt.h" -#include "mic_virtio.h"  /* Debugfs parent dir */  static struct dentry *mic_dbg; @@ -100,190 +99,6 @@ static const struct file_operations post_code_ops = {  	.release = mic_post_code_debug_release  }; -static int mic_dp_show(struct seq_file *s, void *pos) -{ -	struct mic_device *mdev = s->private; -	struct mic_device_desc *d; -	struct mic_device_ctrl *dc; -	struct mic_vqconfig *vqconfig; -	__u32 *features; -	__u8 *config; -	struct mic_bootparam *bootparam = mdev->dp; -	int i, j; - -	seq_printf(s, "Bootparam: magic 0x%x\n", -		   bootparam->magic); -	seq_printf(s, "Bootparam: h2c_config_db %d\n", -		   bootparam->h2c_config_db); -	seq_printf(s, "Bootparam: node_id %d\n", -		   bootparam->node_id); -	seq_printf(s, "Bootparam: c2h_scif_db %d\n", -		   bootparam->c2h_scif_db); -	seq_printf(s, "Bootparam: h2c_scif_db %d\n", -		   bootparam->h2c_scif_db); -	seq_printf(s, "Bootparam: scif_host_dma_addr 0x%llx\n", -		   bootparam->scif_host_dma_addr); -	seq_printf(s, "Bootparam: scif_card_dma_addr 0x%llx\n", -		   bootparam->scif_card_dma_addr); - - -	for (i = sizeof(*bootparam); i < MIC_DP_SIZE; -	     i += mic_total_desc_size(d)) { -		d = mdev->dp + i; -		dc = (void *)d + mic_aligned_desc_size(d); - -		/* end of list */ -		if (d->type == 0) -			break; - -		if (d->type == -1) -			continue; - -		seq_printf(s, "Type %d ", d->type); -		seq_printf(s, "Num VQ %d ", d->num_vq); -		seq_printf(s, "Feature Len %d\n", d->feature_len); -		seq_printf(s, "Config Len %d ", d->config_len); -		seq_printf(s, "Shutdown Status %d\n", d->status); - -		for (j = 0; j < d->num_vq; j++) { -			vqconfig = mic_vq_config(d) + j; -			seq_printf(s, "vqconfig[%d]: ", j); -			seq_printf(s, "address 0x%llx ", vqconfig->address); -			seq_printf(s, "num %d ", vqconfig->num); -			seq_printf(s, "used address 0x%llx\n", -				   vqconfig->used_address); -		} - -		features = (__u32 *)mic_vq_features(d); -		seq_printf(s, "Features: Host 0x%x ", features[0]); -		seq_printf(s, "Guest 0x%x\n", features[1]); - -		config = mic_vq_configspace(d); -		for (j = 0; j < d->config_len; j++) -			seq_printf(s, "config[%d]=%d\n", j, config[j]); - -		seq_puts(s, "Device control:\n"); -		seq_printf(s, "Config Change %d ", dc->config_change); -		seq_printf(s, "Vdev reset %d\n", dc->vdev_reset); -		seq_printf(s, "Guest Ack %d ", dc->guest_ack); -		seq_printf(s, "Host ack %d\n", dc->host_ack); -		seq_printf(s, "Used address updated %d ", -			   dc->used_address_updated); -		seq_printf(s, "Vdev 0x%llx\n", dc->vdev); -		seq_printf(s, "c2h doorbell %d ", dc->c2h_vdev_db); -		seq_printf(s, "h2c doorbell %d\n", dc->h2c_vdev_db); -	} - -	return 0; -} - -static int mic_dp_debug_open(struct inode *inode, struct file *file) -{ -	return single_open(file, mic_dp_show, inode->i_private); -} - -static int mic_dp_debug_release(struct inode *inode, struct file *file) -{ -	return single_release(inode, file); -} - -static const struct file_operations dp_ops = { -	.owner   = THIS_MODULE, -	.open    = mic_dp_debug_open, -	.read    = seq_read, -	.llseek  = seq_lseek, -	.release = mic_dp_debug_release -}; - -static int mic_vdev_info_show(struct seq_file *s, void *unused) -{ -	struct mic_device *mdev = s->private; -	struct list_head *pos, *tmp; -	struct mic_vdev *mvdev; -	int i, j; - -	mutex_lock(&mdev->mic_mutex); -	list_for_each_safe(pos, tmp, &mdev->vdev_list) { -		mvdev = list_entry(pos, struct mic_vdev, list); -		seq_printf(s, "VDEV type %d state %s in %ld out %ld\n", -			   mvdev->virtio_id, -			   mic_vdevup(mvdev) ? "UP" : "DOWN", -			   mvdev->in_bytes, -			   mvdev->out_bytes); -		for (i = 0; i < MIC_MAX_VRINGS; i++) { -			struct vring_desc *desc; -			struct vring_avail *avail; -			struct vring_used *used; -			struct mic_vringh *mvr = &mvdev->mvr[i]; -			struct vringh *vrh = &mvr->vrh; -			int num = vrh->vring.num; -			if (!num) -				continue; -			desc = vrh->vring.desc; -			seq_printf(s, "vring i %d avail_idx %d", -				   i, mvr->vring.info->avail_idx & (num - 1)); -			seq_printf(s, " vring i %d avail_idx %d\n", -				   i, mvr->vring.info->avail_idx); -			seq_printf(s, "vrh i %d weak_barriers %d", -				   i, vrh->weak_barriers); -			seq_printf(s, " last_avail_idx %d last_used_idx %d", -				   vrh->last_avail_idx, vrh->last_used_idx); -			seq_printf(s, " completed %d\n", vrh->completed); -			for (j = 0; j < num; j++) { -				seq_printf(s, "desc[%d] addr 0x%llx len %d", -					   j, desc->addr, desc->len); -				seq_printf(s, " flags 0x%x next %d\n", -					   desc->flags, desc->next); -				desc++; -			} -			avail = vrh->vring.avail; -			seq_printf(s, "avail flags 0x%x idx %d\n", -				   vringh16_to_cpu(vrh, avail->flags), -				   vringh16_to_cpu(vrh, avail->idx) & (num - 1)); -			seq_printf(s, "avail flags 0x%x idx %d\n", -				   vringh16_to_cpu(vrh, avail->flags), -				   vringh16_to_cpu(vrh, avail->idx)); -			for (j = 0; j < num; j++) -				seq_printf(s, "avail ring[%d] %d\n", -					   j, avail->ring[j]); -			used = vrh->vring.used; -			seq_printf(s, "used flags 0x%x idx %d\n", -				   vringh16_to_cpu(vrh, used->flags), -				   vringh16_to_cpu(vrh, used->idx) & (num - 1)); -			seq_printf(s, "used flags 0x%x idx %d\n", -				   vringh16_to_cpu(vrh, used->flags), -				   vringh16_to_cpu(vrh, used->idx)); -			for (j = 0; j < num; j++) -				seq_printf(s, "used ring[%d] id %d len %d\n", -					   j, vringh32_to_cpu(vrh, -							      used->ring[j].id), -					   vringh32_to_cpu(vrh, -							   used->ring[j].len)); -		} -	} -	mutex_unlock(&mdev->mic_mutex); - -	return 0; -} - -static int mic_vdev_info_debug_open(struct inode *inode, struct file *file) -{ -	return single_open(file, mic_vdev_info_show, inode->i_private); -} - -static int mic_vdev_info_debug_release(struct inode *inode, struct file *file) -{ -	return single_release(inode, file); -} - -static const struct file_operations vdev_info_ops = { -	.owner   = THIS_MODULE, -	.open    = mic_vdev_info_debug_open, -	.read    = seq_read, -	.llseek  = seq_lseek, -	.release = mic_vdev_info_debug_release -}; -  static int mic_msi_irq_info_show(struct seq_file *s, void *pos)  {  	struct mic_device *mdev  = s->private; @@ -367,11 +182,6 @@ void mic_create_debug_dir(struct mic_device *mdev)  	debugfs_create_file("post_code", 0444, mdev->dbg_dir, mdev,  			    &post_code_ops); -	debugfs_create_file("dp", 0444, mdev->dbg_dir, mdev, &dp_ops); - -	debugfs_create_file("vdev_info", 0444, mdev->dbg_dir, mdev, -			    &vdev_info_ops); -  	debugfs_create_file("msi_irq_info", 0444, mdev->dbg_dir, mdev,  			    &msi_irq_info_ops);  } diff --git a/drivers/misc/mic/host/mic_device.h b/drivers/misc/mic/host/mic_device.h index 461184a12fbb..52b12b22f4ae 100644 --- a/drivers/misc/mic/host/mic_device.h +++ b/drivers/misc/mic/host/mic_device.h @@ -29,6 +29,7 @@  #include <linux/miscdevice.h>  #include <linux/mic_bus.h>  #include "../bus/scif_bus.h" +#include "../bus/vop_bus.h"  #include "../bus/cosm_bus.h"  #include "mic_intr.h" @@ -64,13 +65,11 @@ extern struct cosm_hw_ops cosm_hw_ops;   * @bootaddr: MIC boot address.   * @dp: virtio device page   * @dp_dma_addr: virtio device page DMA address. - * @name: name for the misc char device - * @miscdev: registered misc char device - * @vdev_list: list of virtio devices.   * @dma_mbdev: MIC BUS DMA device.   * @dma_ch - Array of DMA channels   * @num_dma_ch - Number of DMA channels available   * @scdev: SCIF device on the SCIF virtual bus. + * @vpdev: Virtio over PCIe device on the VOP virtual bus.   * @cosm_dev: COSM device   */  struct mic_device { @@ -91,13 +90,11 @@ struct mic_device {  	u32 bootaddr;  	void *dp;  	dma_addr_t dp_dma_addr; -	char name[16]; -	struct miscdevice miscdev; -	struct list_head vdev_list;  	struct mbus_device *dma_mbdev;  	struct dma_chan *dma_ch[MIC_MAX_DMA_CHAN];  	int num_dma_ch;  	struct scif_hw_dev *scdev; +	struct vop_device *vpdev;  	struct cosm_device *cosm_dev;  }; diff --git a/drivers/misc/mic/host/mic_fops.c b/drivers/misc/mic/host/mic_fops.c deleted file mode 100644 index 8cc1d90cd949..000000000000 --- a/drivers/misc/mic/host/mic_fops.c +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Intel MIC Platform Software Stack (MPSS) - * - * Copyright(c) 2013 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * The full GNU General Public License is included in this distribution in - * the file called "COPYING". - * - * Intel MIC Host driver. - * - */ -#include <linux/poll.h> -#include <linux/pci.h> - -#include <linux/mic_common.h> -#include "../common/mic_dev.h" -#include "mic_device.h" -#include "mic_fops.h" -#include "mic_virtio.h" - -int mic_open(struct inode *inode, struct file *f) -{ -	struct mic_vdev *mvdev; -	struct mic_device *mdev = container_of(f->private_data, -		struct mic_device, miscdev); - -	mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL); -	if (!mvdev) -		return -ENOMEM; - -	init_waitqueue_head(&mvdev->waitq); -	INIT_LIST_HEAD(&mvdev->list); -	mvdev->mdev = mdev; -	mvdev->virtio_id = -1; - -	f->private_data = mvdev; -	return 0; -} - -int mic_release(struct inode *inode, struct file *f) -{ -	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; - -	if (-1 != mvdev->virtio_id) -		mic_virtio_del_device(mvdev); -	f->private_data = NULL; -	kfree(mvdev); -	return 0; -} - -long mic_ioctl(struct file *f, unsigned int cmd, unsigned long arg) -{ -	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; -	void __user *argp = (void __user *)arg; -	int ret; - -	switch (cmd) { -	case MIC_VIRTIO_ADD_DEVICE: -	{ -		ret = mic_virtio_add_device(mvdev, argp); -		if (ret < 0) { -			dev_err(mic_dev(mvdev), -				"%s %d errno ret %d\n", -				__func__, __LINE__, ret); -			return ret; -		} -		break; -	} -	case MIC_VIRTIO_COPY_DESC: -	{ -		struct mic_copy_desc copy; - -		ret = mic_vdev_inited(mvdev); -		if (ret) -			return ret; - -		if (copy_from_user(©, argp, sizeof(copy))) -			return -EFAULT; - -		dev_dbg(mic_dev(mvdev), -			"%s %d === iovcnt 0x%x vr_idx 0x%x update_used %d\n", -			__func__, __LINE__, copy.iovcnt, copy.vr_idx, -			copy.update_used); - -		ret = mic_virtio_copy_desc(mvdev, ©); -		if (ret < 0) { -			dev_err(mic_dev(mvdev), -				"%s %d errno ret %d\n", -				__func__, __LINE__, ret); -			return ret; -		} -		if (copy_to_user( -			&((struct mic_copy_desc __user *)argp)->out_len, -			©.out_len, sizeof(copy.out_len))) { -			dev_err(mic_dev(mvdev), "%s %d errno ret %d\n", -				__func__, __LINE__, -EFAULT); -			return -EFAULT; -		} -		break; -	} -	case MIC_VIRTIO_CONFIG_CHANGE: -	{ -		ret = mic_vdev_inited(mvdev); -		if (ret) -			return ret; - -		ret = mic_virtio_config_change(mvdev, argp); -		if (ret < 0) { -			dev_err(mic_dev(mvdev), -				"%s %d errno ret %d\n", -				__func__, __LINE__, ret); -			return ret; -		} -		break; -	} -	default: -		return -ENOIOCTLCMD; -	}; -	return 0; -} - -/* - * We return POLLIN | POLLOUT from poll when new buffers are enqueued, and - * not when previously enqueued buffers may be available. This means that - * in the card->host (TX) path, when userspace is unblocked by poll it - * must drain all available descriptors or it can stall. - */ -unsigned int mic_poll(struct file *f, poll_table *wait) -{ -	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; -	int mask = 0; - -	poll_wait(f, &mvdev->waitq, wait); - -	if (mic_vdev_inited(mvdev)) { -		mask = POLLERR; -	} else if (mvdev->poll_wake) { -		mvdev->poll_wake = 0; -		mask = POLLIN | POLLOUT; -	} - -	return mask; -} - -static inline int -mic_query_offset(struct mic_vdev *mvdev, unsigned long offset, -		 unsigned long *size, unsigned long *pa) -{ -	struct mic_device *mdev = mvdev->mdev; -	unsigned long start = MIC_DP_SIZE; -	int i; - -	/* -	 * MMAP interface is as follows: -	 * offset				region -	 * 0x0					virtio device_page -	 * 0x1000				first vring -	 * 0x1000 + size of 1st vring		second vring -	 * .... -	 */ -	if (!offset) { -		*pa = virt_to_phys(mdev->dp); -		*size = MIC_DP_SIZE; -		return 0; -	} - -	for (i = 0; i < mvdev->dd->num_vq; i++) { -		struct mic_vringh *mvr = &mvdev->mvr[i]; -		if (offset == start) { -			*pa = virt_to_phys(mvr->vring.va); -			*size = mvr->vring.len; -			return 0; -		} -		start += mvr->vring.len; -	} -	return -1; -} - -/* - * Maps the device page and virtio rings to user space for readonly access. - */ -int -mic_mmap(struct file *f, struct vm_area_struct *vma) -{ -	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; -	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; -	unsigned long pa, size = vma->vm_end - vma->vm_start, size_rem = size; -	int i, err; - -	err = mic_vdev_inited(mvdev); -	if (err) -		return err; - -	if (vma->vm_flags & VM_WRITE) -		return -EACCES; - -	while (size_rem) { -		i = mic_query_offset(mvdev, offset, &size, &pa); -		if (i < 0) -			return -EINVAL; -		err = remap_pfn_range(vma, vma->vm_start + offset, -			pa >> PAGE_SHIFT, size, vma->vm_page_prot); -		if (err) -			return err; -		dev_dbg(mic_dev(mvdev), -			"%s %d type %d size 0x%lx off 0x%lx pa 0x%lx vma 0x%lx\n", -			__func__, __LINE__, mvdev->virtio_id, size, offset, -			pa, vma->vm_start + offset); -		size_rem -= size; -		offset += size; -	} -	return 0; -} diff --git a/drivers/misc/mic/host/mic_fops.h b/drivers/misc/mic/host/mic_fops.h deleted file mode 100644 index dc3893dff667..000000000000 --- a/drivers/misc/mic/host/mic_fops.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Intel MIC Platform Software Stack (MPSS) - * - * Copyright(c) 2013 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * The full GNU General Public License is included in this distribution in - * the file called "COPYING". - * - * Intel MIC Host driver. - * - */ -#ifndef _MIC_FOPS_H_ -#define _MIC_FOPS_H_ - -int mic_open(struct inode *inode, struct file *filp); -int mic_release(struct inode *inode, struct file *filp); -ssize_t mic_read(struct file *filp, char __user *buf, -			size_t count, loff_t *pos); -long mic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); -int mic_mmap(struct file *f, struct vm_area_struct *vma); -unsigned int mic_poll(struct file *f, poll_table *wait); - -#endif diff --git a/drivers/misc/mic/host/mic_main.c b/drivers/misc/mic/host/mic_main.c index 153894e7ed5b..035be3e9ceba 100644 --- a/drivers/misc/mic/host/mic_main.c +++ b/drivers/misc/mic/host/mic_main.c @@ -27,8 +27,6 @@  #include "mic_device.h"  #include "mic_x100.h"  #include "mic_smpt.h" -#include "mic_fops.h" -#include "mic_virtio.h"  static const char mic_driver_name[] = "mic"; @@ -57,17 +55,6 @@ MODULE_DEVICE_TABLE(pci, mic_pci_tbl);  /* ID allocator for MIC devices */  static struct ida g_mic_ida; -/* Base device node number for MIC devices */ -static dev_t g_mic_devno; - -static const struct file_operations mic_fops = { -	.open = mic_open, -	.release = mic_release, -	.unlocked_ioctl = mic_ioctl, -	.poll = mic_poll, -	.mmap = mic_mmap, -	.owner = THIS_MODULE, -};  /* Initialize the device page */  static int mic_dp_init(struct mic_device *mdev) @@ -169,7 +156,6 @@ mic_device_init(struct mic_device *mdev, struct pci_dev *pdev)  	mic_ops_init(mdev);  	mutex_init(&mdev->mic_mutex);  	mdev->irq_info.next_avail_src = 0; -	INIT_LIST_HEAD(&mdev->vdev_list);  }  /** @@ -259,30 +245,15 @@ static int mic_probe(struct pci_dev *pdev,  		goto smpt_uninit;  	}  	mic_bootparam_init(mdev); -  	mic_create_debug_dir(mdev); -	mdev->miscdev.minor = MISC_DYNAMIC_MINOR; -	snprintf(mdev->name, sizeof(mdev->name), "mic%d", mdev->id); -	mdev->miscdev.name = mdev->name; -	mdev->miscdev.fops = &mic_fops; -	mdev->miscdev.parent = &mdev->pdev->dev; -	rc = misc_register(&mdev->miscdev); -	if (rc) { -		dev_err(&pdev->dev, "misc_register err id %d rc %d\n", -			mdev->id, rc); -		goto cleanup_debug_dir; -	} -  	mdev->cosm_dev = cosm_register_device(&mdev->pdev->dev, &cosm_hw_ops);  	if (IS_ERR(mdev->cosm_dev)) {  		rc = PTR_ERR(mdev->cosm_dev);  		dev_err(&pdev->dev, "cosm_add_device failed rc %d\n", rc); -		goto misc_dereg; +		goto cleanup_debug_dir;  	}  	return 0; -misc_dereg: -	misc_deregister(&mdev->miscdev);  cleanup_debug_dir:  	mic_delete_debug_dir(mdev);  	mic_dp_uninit(mdev); @@ -323,7 +294,6 @@ static void mic_remove(struct pci_dev *pdev)  		return;  	cosm_unregister_device(mdev->cosm_dev); -	misc_deregister(&mdev->miscdev);  	mic_delete_debug_dir(mdev);  	mic_dp_uninit(mdev);  	mic_smpt_uninit(mdev); @@ -347,26 +317,18 @@ static int __init mic_init(void)  {  	int ret; -	ret = alloc_chrdev_region(&g_mic_devno, 0, -				  MIC_MAX_NUM_DEVS, mic_driver_name); -	if (ret) { -		pr_err("alloc_chrdev_region failed ret %d\n", ret); -		goto error; -	} - +	request_module("mic_x100_dma");  	mic_init_debugfs();  	ida_init(&g_mic_ida);  	ret = pci_register_driver(&mic_driver);  	if (ret) {  		pr_err("pci_register_driver failed ret %d\n", ret); -		goto cleanup_chrdev; +		goto cleanup_debugfs;  	} -	return ret; -cleanup_chrdev: +	return 0; +cleanup_debugfs:  	ida_destroy(&g_mic_ida);  	mic_exit_debugfs(); -	unregister_chrdev_region(g_mic_devno, MIC_MAX_NUM_DEVS); -error:  	return ret;  } @@ -375,7 +337,6 @@ static void __exit mic_exit(void)  	pci_unregister_driver(&mic_driver);  	ida_destroy(&g_mic_ida);  	mic_exit_debugfs(); -	unregister_chrdev_region(g_mic_devno, MIC_MAX_NUM_DEVS);  }  module_init(mic_init); diff --git a/drivers/misc/mic/host/mic_virtio.c b/drivers/misc/mic/host/mic_virtio.c deleted file mode 100644 index 58b107a24a8b..000000000000 --- a/drivers/misc/mic/host/mic_virtio.c +++ /dev/null @@ -1,811 +0,0 @@ -/* - * Intel MIC Platform Software Stack (MPSS) - * - * Copyright(c) 2013 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * The full GNU General Public License is included in this distribution in - * the file called "COPYING". - * - * Intel MIC Host driver. - * - */ -#include <linux/pci.h> -#include <linux/sched.h> -#include <linux/uaccess.h> -#include <linux/dmaengine.h> -#include <linux/mic_common.h> -#include "../common/mic_dev.h" -#include "mic_device.h" -#include "mic_smpt.h" -#include "mic_virtio.h" - -/* - * Size of the internal buffer used during DMA's as an intermediate buffer - * for copy to/from user. - */ -#define MIC_INT_DMA_BUF_SIZE PAGE_ALIGN(64 * 1024ULL) - -static int mic_sync_dma(struct mic_device *mdev, dma_addr_t dst, -			dma_addr_t src, size_t len) -{ -	int err = 0; -	struct dma_async_tx_descriptor *tx; -	struct dma_chan *mic_ch = mdev->dma_ch[0]; - -	if (!mic_ch) { -		err = -EBUSY; -		goto error; -	} - -	tx = mic_ch->device->device_prep_dma_memcpy(mic_ch, dst, src, len, -						    DMA_PREP_FENCE); -	if (!tx) { -		err = -ENOMEM; -		goto error; -	} else { -		dma_cookie_t cookie = tx->tx_submit(tx); - -		err = dma_submit_error(cookie); -		if (err) -			goto error; -		err = dma_sync_wait(mic_ch, cookie); -	} -error: -	if (err) -		dev_err(&mdev->pdev->dev, "%s %d err %d\n", -			__func__, __LINE__, err); -	return err; -} - -/* - * Initiates the copies across the PCIe bus from card memory to a user - * space buffer. When transfers are done using DMA, source/destination - * addresses and transfer length must follow the alignment requirements of - * the MIC DMA engine. - */ -static int mic_virtio_copy_to_user(struct mic_vdev *mvdev, void __user *ubuf, -				   size_t len, u64 daddr, size_t dlen, -				   int vr_idx) -{ -	struct mic_device *mdev = mvdev->mdev; -	void __iomem *dbuf = mdev->aper.va + daddr; -	struct mic_vringh *mvr = &mvdev->mvr[vr_idx]; -	size_t dma_alignment = 1 << mdev->dma_ch[0]->device->copy_align; -	size_t dma_offset; -	size_t partlen; -	int err; - -	dma_offset = daddr - round_down(daddr, dma_alignment); -	daddr -= dma_offset; -	len += dma_offset; - -	while (len) { -		partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE); - -		err = mic_sync_dma(mdev, mvr->buf_da, daddr, -				   ALIGN(partlen, dma_alignment)); -		if (err) -			goto err; - -		if (copy_to_user(ubuf, mvr->buf + dma_offset, -				 partlen - dma_offset)) { -			err = -EFAULT; -			goto err; -		} -		daddr += partlen; -		ubuf += partlen; -		dbuf += partlen; -		mvdev->in_bytes_dma += partlen; -		mvdev->in_bytes += partlen; -		len -= partlen; -		dma_offset = 0; -	} -	return 0; -err: -	dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err); -	return err; -} - -/* - * Initiates copies across the PCIe bus from a user space buffer to card - * memory. When transfers are done using DMA, source/destination addresses - * and transfer length must follow the alignment requirements of the MIC - * DMA engine. - */ -static int mic_virtio_copy_from_user(struct mic_vdev *mvdev, void __user *ubuf, -				     size_t len, u64 daddr, size_t dlen, -				     int vr_idx) -{ -	struct mic_device *mdev = mvdev->mdev; -	void __iomem *dbuf = mdev->aper.va + daddr; -	struct mic_vringh *mvr = &mvdev->mvr[vr_idx]; -	size_t dma_alignment = 1 << mdev->dma_ch[0]->device->copy_align; -	size_t partlen; -	int err; - -	if (daddr & (dma_alignment - 1)) { -		mvdev->tx_dst_unaligned += len; -		goto memcpy; -	} else if (ALIGN(len, dma_alignment) > dlen) { -		mvdev->tx_len_unaligned += len; -		goto memcpy; -	} - -	while (len) { -		partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE); - -		if (copy_from_user(mvr->buf, ubuf, partlen)) { -			err = -EFAULT; -			goto err; -		} -		err = mic_sync_dma(mdev, daddr, mvr->buf_da, -				   ALIGN(partlen, dma_alignment)); -		if (err) -			goto err; -		daddr += partlen; -		ubuf += partlen; -		dbuf += partlen; -		mvdev->out_bytes_dma += partlen; -		mvdev->out_bytes += partlen; -		len -= partlen; -	} -memcpy: -	/* -	 * We are copying to IO below and should ideally use something -	 * like copy_from_user_toio(..) if it existed. -	 */ -	if (copy_from_user((void __force *)dbuf, ubuf, len)) { -		err = -EFAULT; -		goto err; -	} -	mvdev->out_bytes += len; -	return 0; -err: -	dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err); -	return err; -} - -#define MIC_VRINGH_READ true - -/* The function to call to notify the card about added buffers */ -static void mic_notify(struct vringh *vrh) -{ -	struct mic_vringh *mvrh = container_of(vrh, struct mic_vringh, vrh); -	struct mic_vdev *mvdev = mvrh->mvdev; -	s8 db = mvdev->dc->h2c_vdev_db; - -	if (db != -1) -		mvdev->mdev->ops->send_intr(mvdev->mdev, db); -} - -/* Determine the total number of bytes consumed in a VRINGH KIOV */ -static inline u32 mic_vringh_iov_consumed(struct vringh_kiov *iov) -{ -	int i; -	u32 total = iov->consumed; - -	for (i = 0; i < iov->i; i++) -		total += iov->iov[i].iov_len; -	return total; -} - -/* - * Traverse the VRINGH KIOV and issue the APIs to trigger the copies. - * This API is heavily based on the vringh_iov_xfer(..) implementation - * in vringh.c. The reason we cannot reuse vringh_iov_pull_kern(..) - * and vringh_iov_push_kern(..) directly is because there is no - * way to override the VRINGH xfer(..) routines as of v3.10. - */ -static int mic_vringh_copy(struct mic_vdev *mvdev, struct vringh_kiov *iov, -			void __user *ubuf, size_t len, bool read, int vr_idx, -			size_t *out_len) -{ -	int ret = 0; -	size_t partlen, tot_len = 0; - -	while (len && iov->i < iov->used) { -		partlen = min(iov->iov[iov->i].iov_len, len); -		if (read) -			ret = mic_virtio_copy_to_user(mvdev, ubuf, partlen, -						(u64)iov->iov[iov->i].iov_base, -						iov->iov[iov->i].iov_len, -						vr_idx); -		else -			ret = mic_virtio_copy_from_user(mvdev, ubuf, partlen, -						(u64)iov->iov[iov->i].iov_base, -						iov->iov[iov->i].iov_len, -						vr_idx); -		if (ret) { -			dev_err(mic_dev(mvdev), "%s %d err %d\n", -				__func__, __LINE__, ret); -			break; -		} -		len -= partlen; -		ubuf += partlen; -		tot_len += partlen; -		iov->consumed += partlen; -		iov->iov[iov->i].iov_len -= partlen; -		iov->iov[iov->i].iov_base += partlen; -		if (!iov->iov[iov->i].iov_len) { -			/* Fix up old iov element then increment. */ -			iov->iov[iov->i].iov_len = iov->consumed; -			iov->iov[iov->i].iov_base -= iov->consumed; - -			iov->consumed = 0; -			iov->i++; -		} -	} -	*out_len = tot_len; -	return ret; -} - -/* - * Use the standard VRINGH infrastructure in the kernel to fetch new - * descriptors, initiate the copies and update the used ring. - */ -static int _mic_virtio_copy(struct mic_vdev *mvdev, -	struct mic_copy_desc *copy) -{ -	int ret = 0; -	u32 iovcnt = copy->iovcnt; -	struct iovec iov; -	struct iovec __user *u_iov = copy->iov; -	void __user *ubuf = NULL; -	struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx]; -	struct vringh_kiov *riov = &mvr->riov; -	struct vringh_kiov *wiov = &mvr->wiov; -	struct vringh *vrh = &mvr->vrh; -	u16 *head = &mvr->head; -	struct mic_vring *vr = &mvr->vring; -	size_t len = 0, out_len; - -	copy->out_len = 0; -	/* Fetch a new IOVEC if all previous elements have been processed */ -	if (riov->i == riov->used && wiov->i == wiov->used) { -		ret = vringh_getdesc_kern(vrh, riov, wiov, -				head, GFP_KERNEL); -		/* Check if there are available descriptors */ -		if (ret <= 0) -			return ret; -	} -	while (iovcnt) { -		if (!len) { -			/* Copy over a new iovec from user space. */ -			ret = copy_from_user(&iov, u_iov, sizeof(*u_iov)); -			if (ret) { -				ret = -EINVAL; -				dev_err(mic_dev(mvdev), "%s %d err %d\n", -					__func__, __LINE__, ret); -				break; -			} -			len = iov.iov_len; -			ubuf = iov.iov_base; -		} -		/* Issue all the read descriptors first */ -		ret = mic_vringh_copy(mvdev, riov, ubuf, len, MIC_VRINGH_READ, -				      copy->vr_idx, &out_len); -		if (ret) { -			dev_err(mic_dev(mvdev), "%s %d err %d\n", -				__func__, __LINE__, ret); -			break; -		} -		len -= out_len; -		ubuf += out_len; -		copy->out_len += out_len; -		/* Issue the write descriptors next */ -		ret = mic_vringh_copy(mvdev, wiov, ubuf, len, !MIC_VRINGH_READ, -				      copy->vr_idx, &out_len); -		if (ret) { -			dev_err(mic_dev(mvdev), "%s %d err %d\n", -				__func__, __LINE__, ret); -			break; -		} -		len -= out_len; -		ubuf += out_len; -		copy->out_len += out_len; -		if (!len) { -			/* One user space iovec is now completed */ -			iovcnt--; -			u_iov++; -		} -		/* Exit loop if all elements in KIOVs have been processed. */ -		if (riov->i == riov->used && wiov->i == wiov->used) -			break; -	} -	/* -	 * Update the used ring if a descriptor was available and some data was -	 * copied in/out and the user asked for a used ring update. -	 */ -	if (*head != USHRT_MAX && copy->out_len && copy->update_used) { -		u32 total = 0; - -		/* Determine the total data consumed */ -		total += mic_vringh_iov_consumed(riov); -		total += mic_vringh_iov_consumed(wiov); -		vringh_complete_kern(vrh, *head, total); -		*head = USHRT_MAX; -		if (vringh_need_notify_kern(vrh) > 0) -			vringh_notify(vrh); -		vringh_kiov_cleanup(riov); -		vringh_kiov_cleanup(wiov); -		/* Update avail idx for user space */ -		vr->info->avail_idx = vrh->last_avail_idx; -	} -	return ret; -} - -static inline int mic_verify_copy_args(struct mic_vdev *mvdev, -		struct mic_copy_desc *copy) -{ -	if (copy->vr_idx >= mvdev->dd->num_vq) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, -EINVAL); -		return -EINVAL; -	} -	return 0; -} - -/* Copy a specified number of virtio descriptors in a chain */ -int mic_virtio_copy_desc(struct mic_vdev *mvdev, -		struct mic_copy_desc *copy) -{ -	int err; -	struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx]; - -	err = mic_verify_copy_args(mvdev, copy); -	if (err) -		return err; - -	mutex_lock(&mvr->vr_mutex); -	if (!mic_vdevup(mvdev)) { -		err = -ENODEV; -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, err); -		goto err; -	} -	err = _mic_virtio_copy(mvdev, copy); -	if (err) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, err); -	} -err: -	mutex_unlock(&mvr->vr_mutex); -	return err; -} - -static void mic_virtio_init_post(struct mic_vdev *mvdev) -{ -	struct mic_vqconfig *vqconfig = mic_vq_config(mvdev->dd); -	int i; - -	for (i = 0; i < mvdev->dd->num_vq; i++) { -		if (!le64_to_cpu(vqconfig[i].used_address)) { -			dev_warn(mic_dev(mvdev), "used_address zero??\n"); -			continue; -		} -		mvdev->mvr[i].vrh.vring.used = -			(void __force *)mvdev->mdev->aper.va + -			le64_to_cpu(vqconfig[i].used_address); -	} - -	mvdev->dc->used_address_updated = 0; - -	dev_dbg(mic_dev(mvdev), "%s: device type %d LINKUP\n", -		__func__, mvdev->virtio_id); -} - -static inline void mic_virtio_device_reset(struct mic_vdev *mvdev) -{ -	int i; - -	dev_dbg(mic_dev(mvdev), "%s: status %d device type %d RESET\n", -		__func__, mvdev->dd->status, mvdev->virtio_id); - -	for (i = 0; i < mvdev->dd->num_vq; i++) -		/* -		 * Avoid lockdep false positive. The + 1 is for the mic -		 * mutex which is held in the reset devices code path. -		 */ -		mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1); - -	/* 0 status means "reset" */ -	mvdev->dd->status = 0; -	mvdev->dc->vdev_reset = 0; -	mvdev->dc->host_ack = 1; - -	for (i = 0; i < mvdev->dd->num_vq; i++) { -		struct vringh *vrh = &mvdev->mvr[i].vrh; -		mvdev->mvr[i].vring.info->avail_idx = 0; -		vrh->completed = 0; -		vrh->last_avail_idx = 0; -		vrh->last_used_idx = 0; -	} - -	for (i = 0; i < mvdev->dd->num_vq; i++) -		mutex_unlock(&mvdev->mvr[i].vr_mutex); -} - -void mic_virtio_reset_devices(struct mic_device *mdev) -{ -	struct list_head *pos, *tmp; -	struct mic_vdev *mvdev; - -	dev_dbg(&mdev->pdev->dev, "%s\n",  __func__); - -	list_for_each_safe(pos, tmp, &mdev->vdev_list) { -		mvdev = list_entry(pos, struct mic_vdev, list); -		mic_virtio_device_reset(mvdev); -		mvdev->poll_wake = 1; -		wake_up(&mvdev->waitq); -	} -} - -void mic_bh_handler(struct work_struct *work) -{ -	struct mic_vdev *mvdev = container_of(work, struct mic_vdev, -			virtio_bh_work); - -	if (mvdev->dc->used_address_updated) -		mic_virtio_init_post(mvdev); - -	if (mvdev->dc->vdev_reset) -		mic_virtio_device_reset(mvdev); - -	mvdev->poll_wake = 1; -	wake_up(&mvdev->waitq); -} - -static irqreturn_t mic_virtio_intr_handler(int irq, void *data) -{ -	struct mic_vdev *mvdev = data; -	struct mic_device *mdev = mvdev->mdev; - -	mdev->ops->intr_workarounds(mdev); -	schedule_work(&mvdev->virtio_bh_work); -	return IRQ_HANDLED; -} - -int mic_virtio_config_change(struct mic_vdev *mvdev, -			void __user *argp) -{ -	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake); -	int ret = 0, retry, i; -	struct mic_bootparam *bootparam = mvdev->mdev->dp; -	s8 db = bootparam->h2c_config_db; - -	mutex_lock(&mvdev->mdev->mic_mutex); -	for (i = 0; i < mvdev->dd->num_vq; i++) -		mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1); - -	if (db == -1 || mvdev->dd->type == -1) { -		ret = -EIO; -		goto exit; -	} - -	if (copy_from_user(mic_vq_configspace(mvdev->dd), -			   argp, mvdev->dd->config_len)) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, -EFAULT); -		ret = -EFAULT; -		goto exit; -	} -	mvdev->dc->config_change = MIC_VIRTIO_PARAM_CONFIG_CHANGED; -	mvdev->mdev->ops->send_intr(mvdev->mdev, db); - -	for (retry = 100; retry--;) { -		ret = wait_event_timeout(wake, -			mvdev->dc->guest_ack, msecs_to_jiffies(100)); -		if (ret) -			break; -	} - -	dev_dbg(mic_dev(mvdev), -		"%s %d retry: %d\n", __func__, __LINE__, retry); -	mvdev->dc->config_change = 0; -	mvdev->dc->guest_ack = 0; -exit: -	for (i = 0; i < mvdev->dd->num_vq; i++) -		mutex_unlock(&mvdev->mvr[i].vr_mutex); -	mutex_unlock(&mvdev->mdev->mic_mutex); -	return ret; -} - -static int mic_copy_dp_entry(struct mic_vdev *mvdev, -					void __user *argp, -					__u8 *type, -					struct mic_device_desc **devpage) -{ -	struct mic_device *mdev = mvdev->mdev; -	struct mic_device_desc dd, *dd_config, *devp; -	struct mic_vqconfig *vqconfig; -	int ret = 0, i; -	bool slot_found = false; - -	if (copy_from_user(&dd, argp, sizeof(dd))) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, -EFAULT); -		return -EFAULT; -	} - -	if (mic_aligned_desc_size(&dd) > MIC_MAX_DESC_BLK_SIZE || -	    dd.num_vq > MIC_MAX_VRINGS) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, -EINVAL); -		return -EINVAL; -	} - -	dd_config = kmalloc(mic_desc_size(&dd), GFP_KERNEL); -	if (dd_config == NULL) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, -ENOMEM); -		return -ENOMEM; -	} -	if (copy_from_user(dd_config, argp, mic_desc_size(&dd))) { -		ret = -EFAULT; -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, ret); -		goto exit; -	} - -	vqconfig = mic_vq_config(dd_config); -	for (i = 0; i < dd.num_vq; i++) { -		if (le16_to_cpu(vqconfig[i].num) > MIC_MAX_VRING_ENTRIES) { -			ret =  -EINVAL; -			dev_err(mic_dev(mvdev), "%s %d err %d\n", -				__func__, __LINE__, ret); -			goto exit; -		} -	} - -	/* Find the first free device page entry */ -	for (i = sizeof(struct mic_bootparam); -		i < MIC_DP_SIZE - mic_total_desc_size(dd_config); -		i += mic_total_desc_size(devp)) { -		devp = mdev->dp + i; -		if (devp->type == 0 || devp->type == -1) { -			slot_found = true; -			break; -		} -	} -	if (!slot_found) { -		ret =  -EINVAL; -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, ret); -		goto exit; -	} -	/* -	 * Save off the type before doing the memcpy. Type will be set in the -	 * end after completing all initialization for the new device. -	 */ -	*type = dd_config->type; -	dd_config->type = 0; -	memcpy(devp, dd_config, mic_desc_size(dd_config)); - -	*devpage = devp; -exit: -	kfree(dd_config); -	return ret; -} - -static void mic_init_device_ctrl(struct mic_vdev *mvdev, -				struct mic_device_desc *devpage) -{ -	struct mic_device_ctrl *dc; - -	dc = (void *)devpage + mic_aligned_desc_size(devpage); - -	dc->config_change = 0; -	dc->guest_ack = 0; -	dc->vdev_reset = 0; -	dc->host_ack = 0; -	dc->used_address_updated = 0; -	dc->c2h_vdev_db = -1; -	dc->h2c_vdev_db = -1; -	mvdev->dc = dc; -} - -int mic_virtio_add_device(struct mic_vdev *mvdev, -			void __user *argp) -{ -	struct mic_device *mdev = mvdev->mdev; -	struct mic_device_desc *dd = NULL; -	struct mic_vqconfig *vqconfig; -	int vr_size, i, j, ret; -	u8 type = 0; -	s8 db; -	char irqname[10]; -	struct mic_bootparam *bootparam = mdev->dp; -	u16 num; -	dma_addr_t vr_addr; - -	mutex_lock(&mdev->mic_mutex); - -	ret = mic_copy_dp_entry(mvdev, argp, &type, &dd); -	if (ret) { -		mutex_unlock(&mdev->mic_mutex); -		return ret; -	} - -	mic_init_device_ctrl(mvdev, dd); - -	mvdev->dd = dd; -	mvdev->virtio_id = type; -	vqconfig = mic_vq_config(dd); -	INIT_WORK(&mvdev->virtio_bh_work, mic_bh_handler); - -	for (i = 0; i < dd->num_vq; i++) { -		struct mic_vringh *mvr = &mvdev->mvr[i]; -		struct mic_vring *vr = &mvdev->mvr[i].vring; -		num = le16_to_cpu(vqconfig[i].num); -		mutex_init(&mvr->vr_mutex); -		vr_size = PAGE_ALIGN(vring_size(num, MIC_VIRTIO_RING_ALIGN) + -			sizeof(struct _mic_vring_info)); -		vr->va = (void *) -			__get_free_pages(GFP_KERNEL | __GFP_ZERO, -					 get_order(vr_size)); -		if (!vr->va) { -			ret = -ENOMEM; -			dev_err(mic_dev(mvdev), "%s %d err %d\n", -				__func__, __LINE__, ret); -			goto err; -		} -		vr->len = vr_size; -		vr->info = vr->va + vring_size(num, MIC_VIRTIO_RING_ALIGN); -		vr->info->magic = cpu_to_le32(MIC_MAGIC + mvdev->virtio_id + i); -		vr_addr = mic_map_single(mdev, vr->va, vr_size); -		if (mic_map_error(vr_addr)) { -			free_pages((unsigned long)vr->va, get_order(vr_size)); -			ret = -ENOMEM; -			dev_err(mic_dev(mvdev), "%s %d err %d\n", -				__func__, __LINE__, ret); -			goto err; -		} -		vqconfig[i].address = cpu_to_le64(vr_addr); - -		vring_init(&vr->vr, num, vr->va, MIC_VIRTIO_RING_ALIGN); -		ret = vringh_init_kern(&mvr->vrh, -			*(u32 *)mic_vq_features(mvdev->dd), num, false, -			vr->vr.desc, vr->vr.avail, vr->vr.used); -		if (ret) { -			dev_err(mic_dev(mvdev), "%s %d err %d\n", -				__func__, __LINE__, ret); -			goto err; -		} -		vringh_kiov_init(&mvr->riov, NULL, 0); -		vringh_kiov_init(&mvr->wiov, NULL, 0); -		mvr->head = USHRT_MAX; -		mvr->mvdev = mvdev; -		mvr->vrh.notify = mic_notify; -		dev_dbg(&mdev->pdev->dev, -			"%s %d index %d va %p info %p vr_size 0x%x\n", -			__func__, __LINE__, i, vr->va, vr->info, vr_size); -		mvr->buf = (void *)__get_free_pages(GFP_KERNEL, -					get_order(MIC_INT_DMA_BUF_SIZE)); -		mvr->buf_da = mic_map_single(mvdev->mdev, mvr->buf, -					  MIC_INT_DMA_BUF_SIZE); -	} - -	snprintf(irqname, sizeof(irqname), "mic%dvirtio%d", mdev->id, -		 mvdev->virtio_id); -	mvdev->virtio_db = mic_next_db(mdev); -	mvdev->virtio_cookie = mic_request_threaded_irq(mdev, -					       mic_virtio_intr_handler, -					       NULL, irqname, mvdev, -					       mvdev->virtio_db, MIC_INTR_DB); -	if (IS_ERR(mvdev->virtio_cookie)) { -		ret = PTR_ERR(mvdev->virtio_cookie); -		dev_dbg(&mdev->pdev->dev, "request irq failed\n"); -		goto err; -	} - -	mvdev->dc->c2h_vdev_db = mvdev->virtio_db; - -	list_add_tail(&mvdev->list, &mdev->vdev_list); -	/* -	 * Order the type update with previous stores. This write barrier -	 * is paired with the corresponding read barrier before the uncached -	 * system memory read of the type, on the card while scanning the -	 * device page. -	 */ -	smp_wmb(); -	dd->type = type; - -	dev_dbg(&mdev->pdev->dev, "Added virtio device id %d\n", dd->type); - -	db = bootparam->h2c_config_db; -	if (db != -1) -		mdev->ops->send_intr(mdev, db); -	mutex_unlock(&mdev->mic_mutex); -	return 0; -err: -	vqconfig = mic_vq_config(dd); -	for (j = 0; j < i; j++) { -		struct mic_vringh *mvr = &mvdev->mvr[j]; -		mic_unmap_single(mdev, le64_to_cpu(vqconfig[j].address), -				 mvr->vring.len); -		free_pages((unsigned long)mvr->vring.va, -			   get_order(mvr->vring.len)); -	} -	mutex_unlock(&mdev->mic_mutex); -	return ret; -} - -void mic_virtio_del_device(struct mic_vdev *mvdev) -{ -	struct list_head *pos, *tmp; -	struct mic_vdev *tmp_mvdev; -	struct mic_device *mdev = mvdev->mdev; -	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake); -	int i, ret, retry; -	struct mic_vqconfig *vqconfig; -	struct mic_bootparam *bootparam = mdev->dp; -	s8 db; - -	mutex_lock(&mdev->mic_mutex); -	db = bootparam->h2c_config_db; -	if (db == -1) -		goto skip_hot_remove; -	dev_dbg(&mdev->pdev->dev, -		"Requesting hot remove id %d\n", mvdev->virtio_id); -	mvdev->dc->config_change = MIC_VIRTIO_PARAM_DEV_REMOVE; -	mdev->ops->send_intr(mdev, db); -	for (retry = 100; retry--;) { -		ret = wait_event_timeout(wake, -			mvdev->dc->guest_ack, msecs_to_jiffies(100)); -		if (ret) -			break; -	} -	dev_dbg(&mdev->pdev->dev, -		"Device id %d config_change %d guest_ack %d retry %d\n", -		mvdev->virtio_id, mvdev->dc->config_change, -		mvdev->dc->guest_ack, retry); -	mvdev->dc->config_change = 0; -	mvdev->dc->guest_ack = 0; -skip_hot_remove: -	mic_free_irq(mdev, mvdev->virtio_cookie, mvdev); -	flush_work(&mvdev->virtio_bh_work); -	vqconfig = mic_vq_config(mvdev->dd); -	for (i = 0; i < mvdev->dd->num_vq; i++) { -		struct mic_vringh *mvr = &mvdev->mvr[i]; - -		mic_unmap_single(mvdev->mdev, mvr->buf_da, -				 MIC_INT_DMA_BUF_SIZE); -		free_pages((unsigned long)mvr->buf, -			   get_order(MIC_INT_DMA_BUF_SIZE)); -		vringh_kiov_cleanup(&mvr->riov); -		vringh_kiov_cleanup(&mvr->wiov); -		mic_unmap_single(mdev, le64_to_cpu(vqconfig[i].address), -				 mvr->vring.len); -		free_pages((unsigned long)mvr->vring.va, -			   get_order(mvr->vring.len)); -	} - -	list_for_each_safe(pos, tmp, &mdev->vdev_list) { -		tmp_mvdev = list_entry(pos, struct mic_vdev, list); -		if (tmp_mvdev == mvdev) { -			list_del(pos); -			dev_dbg(&mdev->pdev->dev, -				"Removing virtio device id %d\n", -				mvdev->virtio_id); -			break; -		} -	} -	/* -	 * Order the type update with previous stores. This write barrier -	 * is paired with the corresponding read barrier before the uncached -	 * system memory read of the type, on the card while scanning the -	 * device page. -	 */ -	smp_wmb(); -	mvdev->dd->type = -1; -	mutex_unlock(&mdev->mic_mutex); -} diff --git a/drivers/misc/mic/host/mic_x100.c b/drivers/misc/mic/host/mic_x100.c index 8118ac48c764..82a973c85b5d 100644 --- a/drivers/misc/mic/host/mic_x100.c +++ b/drivers/misc/mic/host/mic_x100.c @@ -450,26 +450,29 @@ mic_x100_load_firmware(struct mic_device *mdev, const char *buf)  	rc = mic_x100_get_boot_addr(mdev);  	if (rc) -		goto error; +		return rc;  	/* load OS */  	rc = request_firmware(&fw, mdev->cosm_dev->firmware, &mdev->pdev->dev);  	if (rc < 0) {  		dev_err(&mdev->pdev->dev,  			"ramdisk request_firmware failed: %d %s\n",  			rc, mdev->cosm_dev->firmware); -		goto error; +		return rc;  	}  	if (mdev->bootaddr > mdev->aper.len - fw->size) {  		rc = -EINVAL;  		dev_err(&mdev->pdev->dev, "%s %d rc %d bootaddr 0x%x\n",  			__func__, __LINE__, rc, mdev->bootaddr); -		release_firmware(fw);  		goto error;  	}  	memcpy_toio(mdev->aper.va + mdev->bootaddr, fw->data, fw->size);  	mdev->ops->write_spad(mdev, MIC_X100_FW_SIZE, fw->size); -	if (!strcmp(mdev->cosm_dev->bootmode, "flash")) -		goto done; +	if (!strcmp(mdev->cosm_dev->bootmode, "flash")) { +		rc = -EINVAL; +		dev_err(&mdev->pdev->dev, "%s %d rc %d\n", +			__func__, __LINE__, rc); +		goto error; +	}  	/* load command line */  	rc = mic_x100_load_command_line(mdev, fw);  	if (rc) { @@ -481,9 +484,11 @@ mic_x100_load_firmware(struct mic_device *mdev, const char *buf)  	/* load ramdisk */  	if (mdev->cosm_dev->ramdisk)  		rc = mic_x100_load_ramdisk(mdev); + +	return rc; +  error: -	dev_dbg(&mdev->pdev->dev, "%s %d rc %d\n", __func__, __LINE__, rc); -done: +	release_firmware(fw);  	return rc;  } diff --git a/drivers/misc/mic/scif/scif_dma.c b/drivers/misc/mic/scif/scif_dma.c index 95a13c629a8e..cd01a0efda6b 100644 --- a/drivers/misc/mic/scif/scif_dma.c +++ b/drivers/misc/mic/scif/scif_dma.c @@ -74,11 +74,6 @@ struct scif_copy_work {  	bool ordered;  }; -#ifndef list_entry_next -#define list_entry_next(pos, member) \ -	list_entry(pos->member.next, typeof(*pos), member) -#endif -  /**   * scif_reserve_dma_chan:   * @ep: Endpoint Descriptor. @@ -276,13 +271,10 @@ static struct scif_mmu_notif *  scif_find_mmu_notifier(struct mm_struct *mm, struct scif_endpt_rma_info *rma)  {  	struct scif_mmu_notif *mmn; -	struct list_head *item; -	list_for_each(item, &rma->mmn_list) { -		mmn = list_entry(item, struct scif_mmu_notif, list); +	list_for_each_entry(mmn, &rma->mmn_list, list)  		if (mmn->mm == mm)  			return mmn; -	}  	return NULL;  } @@ -293,13 +285,12 @@ scif_add_mmu_notifier(struct mm_struct *mm, struct scif_endpt *ep)  		 = kzalloc(sizeof(*mmn), GFP_KERNEL);  	if (!mmn) -		return ERR_PTR(ENOMEM); +		return ERR_PTR(-ENOMEM);  	scif_init_mmu_notifier(mmn, current->mm, ep); -	if (mmu_notifier_register(&mmn->ep_mmu_notifier, -				  current->mm)) { +	if (mmu_notifier_register(&mmn->ep_mmu_notifier, current->mm)) {  		kfree(mmn); -		return ERR_PTR(EBUSY); +		return ERR_PTR(-EBUSY);  	}  	list_add(&mmn->list, &ep->rma_info.mmn_list);  	return mmn; @@ -851,7 +842,7 @@ static void scif_rma_local_cpu_copy(s64 offset, struct scif_window *window,  		(window->nr_pages << PAGE_SHIFT);  	while (rem_len) {  		if (offset == end_offset) { -			window = list_entry_next(window, list); +			window = list_next_entry(window, list);  			end_offset = window->offset +  				(window->nr_pages << PAGE_SHIFT);  		} @@ -957,7 +948,7 @@ scif_rma_list_dma_copy_unaligned(struct scif_copy_work *work,  	remaining_len -= tail_len;  	while (remaining_len) {  		if (offset == end_offset) { -			window = list_entry_next(window, list); +			window = list_next_entry(window, list);  			end_offset = window->offset +  				(window->nr_pages << PAGE_SHIFT);  		} @@ -1064,7 +1055,7 @@ scif_rma_list_dma_copy_unaligned(struct scif_copy_work *work,  	}  	if (tail_len) {  		if (offset == end_offset) { -			window = list_entry_next(window, list); +			window = list_next_entry(window, list);  			end_offset = window->offset +  				(window->nr_pages << PAGE_SHIFT);  		} @@ -1147,13 +1138,13 @@ static int _scif_rma_list_dma_copy_aligned(struct scif_copy_work *work,  		(dst_window->nr_pages << PAGE_SHIFT);  	while (remaining_len) {  		if (src_offset == end_src_offset) { -			src_window = list_entry_next(src_window, list); +			src_window = list_next_entry(src_window, list);  			end_src_offset = src_window->offset +  				(src_window->nr_pages << PAGE_SHIFT);  			scif_init_window_iter(src_window, &src_win_iter);  		}  		if (dst_offset == end_dst_offset) { -			dst_window = list_entry_next(dst_window, list); +			dst_window = list_next_entry(dst_window, list);  			end_dst_offset = dst_window->offset +  				(dst_window->nr_pages << PAGE_SHIFT);  			scif_init_window_iter(dst_window, &dst_win_iter); @@ -1314,13 +1305,13 @@ static int scif_rma_list_dma_copy_aligned(struct scif_copy_work *work,  	remaining_len -= tail_len;  	while (remaining_len) {  		if (src_offset == end_src_offset) { -			src_window = list_entry_next(src_window, list); +			src_window = list_next_entry(src_window, list);  			end_src_offset = src_window->offset +  				(src_window->nr_pages << PAGE_SHIFT);  			scif_init_window_iter(src_window, &src_win_iter);  		}  		if (dst_offset == end_dst_offset) { -			dst_window = list_entry_next(dst_window, list); +			dst_window = list_next_entry(dst_window, list);  			end_dst_offset = dst_window->offset +  				(dst_window->nr_pages << PAGE_SHIFT);  			scif_init_window_iter(dst_window, &dst_win_iter); @@ -1405,9 +1396,9 @@ static int scif_rma_list_dma_copy_aligned(struct scif_copy_work *work,  	if (remaining_len) {  		loop_len = remaining_len;  		if (src_offset == end_src_offset) -			src_window = list_entry_next(src_window, list); +			src_window = list_next_entry(src_window, list);  		if (dst_offset == end_dst_offset) -			dst_window = list_entry_next(dst_window, list); +			dst_window = list_next_entry(dst_window, list);  		src_dma_addr = __scif_off_to_dma_addr(src_window, src_offset);  		dst_dma_addr = __scif_off_to_dma_addr(dst_window, dst_offset); @@ -1550,12 +1541,12 @@ static int scif_rma_list_cpu_copy(struct scif_copy_work *work)  			end_dst_offset = dst_window->offset +  				(dst_window->nr_pages << PAGE_SHIFT);  			if (src_offset == end_src_offset) { -				src_window = list_entry_next(src_window, list); +				src_window = list_next_entry(src_window, list);  				scif_init_window_iter(src_window,  						      &src_win_iter);  			}  			if (dst_offset == end_dst_offset) { -				dst_window = list_entry_next(dst_window, list); +				dst_window = list_next_entry(dst_window, list);  				scif_init_window_iter(dst_window,  						      &dst_win_iter);  			} @@ -1730,7 +1721,7 @@ static int scif_rma_copy(scif_epd_t epd, off_t loffset, unsigned long addr,  		mutex_lock(&ep->rma_info.mmn_lock);  		mmn = scif_find_mmu_notifier(current->mm, &ep->rma_info);  		if (!mmn) -			scif_add_mmu_notifier(current->mm, ep); +			mmn = scif_add_mmu_notifier(current->mm, ep);  		mutex_unlock(&ep->rma_info.mmn_lock);  		if (IS_ERR(mmn)) {  			scif_put_peer_dev(spdev); diff --git a/drivers/misc/mic/scif/scif_rma.c b/drivers/misc/mic/scif/scif_rma.c index 8310b4dbff06..6a451bd65bf3 100644 --- a/drivers/misc/mic/scif/scif_rma.c +++ b/drivers/misc/mic/scif/scif_rma.c @@ -1511,7 +1511,7 @@ off_t scif_register_pinned_pages(scif_epd_t epd,  	if ((map_flags & SCIF_MAP_FIXED) &&  	    ((ALIGN(offset, PAGE_SIZE) != offset) ||  	    (offset < 0) || -	    (offset + (off_t)len < offset))) +	    (len > LONG_MAX - offset)))  		return -EINVAL;  	might_sleep(); @@ -1614,7 +1614,7 @@ off_t scif_register(scif_epd_t epd, void *addr, size_t len, off_t offset,  	if ((map_flags & SCIF_MAP_FIXED) &&  	    ((ALIGN(offset, PAGE_SIZE) != offset) ||  	    (offset < 0) || -	    (offset + (off_t)len < offset))) +	    (len > LONG_MAX - offset)))  		return -EINVAL;  	/* Unsupported protection requested */ @@ -1732,7 +1732,8 @@ scif_unregister(scif_epd_t epd, off_t offset, size_t len)  	/* Offset is not page aligned or offset+len wraps around */  	if ((ALIGN(offset, PAGE_SIZE) != offset) || -	    (offset + (off_t)len < offset)) +	    (offset < 0) || +	    (len > LONG_MAX - offset))  		return -EINVAL;  	err = scif_verify_epd(ep); diff --git a/drivers/misc/mic/vop/Makefile b/drivers/misc/mic/vop/Makefile new file mode 100644 index 000000000000..78819c8999f1 --- /dev/null +++ b/drivers/misc/mic/vop/Makefile @@ -0,0 +1,9 @@ +# +# Makefile - Intel MIC Linux driver. +# Copyright(c) 2016, Intel Corporation. +# +obj-m := vop.o + +vop-objs += vop_main.o +vop-objs += vop_debugfs.o +vop-objs += vop_vringh.o diff --git a/drivers/misc/mic/vop/vop_debugfs.c b/drivers/misc/mic/vop/vop_debugfs.c new file mode 100644 index 000000000000..ab43884e5cd7 --- /dev/null +++ b/drivers/misc/mic/vop/vop_debugfs.c @@ -0,0 +1,232 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel Virtio Over PCIe (VOP) driver. + * + */ +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include "vop_main.h" + +static int vop_dp_show(struct seq_file *s, void *pos) +{ +	struct mic_device_desc *d; +	struct mic_device_ctrl *dc; +	struct mic_vqconfig *vqconfig; +	__u32 *features; +	__u8 *config; +	struct vop_info *vi = s->private; +	struct vop_device *vpdev = vi->vpdev; +	struct mic_bootparam *bootparam = vpdev->hw_ops->get_dp(vpdev); +	int j, k; + +	seq_printf(s, "Bootparam: magic 0x%x\n", +		   bootparam->magic); +	seq_printf(s, "Bootparam: h2c_config_db %d\n", +		   bootparam->h2c_config_db); +	seq_printf(s, "Bootparam: node_id %d\n", +		   bootparam->node_id); +	seq_printf(s, "Bootparam: c2h_scif_db %d\n", +		   bootparam->c2h_scif_db); +	seq_printf(s, "Bootparam: h2c_scif_db %d\n", +		   bootparam->h2c_scif_db); +	seq_printf(s, "Bootparam: scif_host_dma_addr 0x%llx\n", +		   bootparam->scif_host_dma_addr); +	seq_printf(s, "Bootparam: scif_card_dma_addr 0x%llx\n", +		   bootparam->scif_card_dma_addr); + +	for (j = sizeof(*bootparam); +		j < MIC_DP_SIZE; j += mic_total_desc_size(d)) { +		d = (void *)bootparam + j; +		dc = (void *)d + mic_aligned_desc_size(d); + +		/* end of list */ +		if (d->type == 0) +			break; + +		if (d->type == -1) +			continue; + +		seq_printf(s, "Type %d ", d->type); +		seq_printf(s, "Num VQ %d ", d->num_vq); +		seq_printf(s, "Feature Len %d\n", d->feature_len); +		seq_printf(s, "Config Len %d ", d->config_len); +		seq_printf(s, "Shutdown Status %d\n", d->status); + +		for (k = 0; k < d->num_vq; k++) { +			vqconfig = mic_vq_config(d) + k; +			seq_printf(s, "vqconfig[%d]: ", k); +			seq_printf(s, "address 0x%llx ", +				   vqconfig->address); +			seq_printf(s, "num %d ", vqconfig->num); +			seq_printf(s, "used address 0x%llx\n", +				   vqconfig->used_address); +		} + +		features = (__u32 *)mic_vq_features(d); +		seq_printf(s, "Features: Host 0x%x ", features[0]); +		seq_printf(s, "Guest 0x%x\n", features[1]); + +		config = mic_vq_configspace(d); +		for (k = 0; k < d->config_len; k++) +			seq_printf(s, "config[%d]=%d\n", k, config[k]); + +		seq_puts(s, "Device control:\n"); +		seq_printf(s, "Config Change %d ", dc->config_change); +		seq_printf(s, "Vdev reset %d\n", dc->vdev_reset); +		seq_printf(s, "Guest Ack %d ", dc->guest_ack); +		seq_printf(s, "Host ack %d\n", dc->host_ack); +		seq_printf(s, "Used address updated %d ", +			   dc->used_address_updated); +		seq_printf(s, "Vdev 0x%llx\n", dc->vdev); +		seq_printf(s, "c2h doorbell %d ", dc->c2h_vdev_db); +		seq_printf(s, "h2c doorbell %d\n", dc->h2c_vdev_db); +	} +	schedule_work(&vi->hotplug_work); +	return 0; +} + +static int vop_dp_debug_open(struct inode *inode, struct file *file) +{ +	return single_open(file, vop_dp_show, inode->i_private); +} + +static int vop_dp_debug_release(struct inode *inode, struct file *file) +{ +	return single_release(inode, file); +} + +static const struct file_operations dp_ops = { +	.owner   = THIS_MODULE, +	.open    = vop_dp_debug_open, +	.read    = seq_read, +	.llseek  = seq_lseek, +	.release = vop_dp_debug_release +}; + +static int vop_vdev_info_show(struct seq_file *s, void *unused) +{ +	struct vop_info *vi = s->private; +	struct list_head *pos, *tmp; +	struct vop_vdev *vdev; +	int i, j; + +	mutex_lock(&vi->vop_mutex); +	list_for_each_safe(pos, tmp, &vi->vdev_list) { +		vdev = list_entry(pos, struct vop_vdev, list); +		seq_printf(s, "VDEV type %d state %s in %ld out %ld in_dma %ld out_dma %ld\n", +			   vdev->virtio_id, +			   vop_vdevup(vdev) ? "UP" : "DOWN", +			   vdev->in_bytes, +			   vdev->out_bytes, +			   vdev->in_bytes_dma, +			   vdev->out_bytes_dma); +		for (i = 0; i < MIC_MAX_VRINGS; i++) { +			struct vring_desc *desc; +			struct vring_avail *avail; +			struct vring_used *used; +			struct vop_vringh *vvr = &vdev->vvr[i]; +			struct vringh *vrh = &vvr->vrh; +			int num = vrh->vring.num; + +			if (!num) +				continue; +			desc = vrh->vring.desc; +			seq_printf(s, "vring i %d avail_idx %d", +				   i, vvr->vring.info->avail_idx & (num - 1)); +			seq_printf(s, " vring i %d avail_idx %d\n", +				   i, vvr->vring.info->avail_idx); +			seq_printf(s, "vrh i %d weak_barriers %d", +				   i, vrh->weak_barriers); +			seq_printf(s, " last_avail_idx %d last_used_idx %d", +				   vrh->last_avail_idx, vrh->last_used_idx); +			seq_printf(s, " completed %d\n", vrh->completed); +			for (j = 0; j < num; j++) { +				seq_printf(s, "desc[%d] addr 0x%llx len %d", +					   j, desc->addr, desc->len); +				seq_printf(s, " flags 0x%x next %d\n", +					   desc->flags, desc->next); +				desc++; +			} +			avail = vrh->vring.avail; +			seq_printf(s, "avail flags 0x%x idx %d\n", +				   vringh16_to_cpu(vrh, avail->flags), +				   vringh16_to_cpu(vrh, +						   avail->idx) & (num - 1)); +			seq_printf(s, "avail flags 0x%x idx %d\n", +				   vringh16_to_cpu(vrh, avail->flags), +				   vringh16_to_cpu(vrh, avail->idx)); +			for (j = 0; j < num; j++) +				seq_printf(s, "avail ring[%d] %d\n", +					   j, avail->ring[j]); +			used = vrh->vring.used; +			seq_printf(s, "used flags 0x%x idx %d\n", +				   vringh16_to_cpu(vrh, used->flags), +				   vringh16_to_cpu(vrh, used->idx) & (num - 1)); +			seq_printf(s, "used flags 0x%x idx %d\n", +				   vringh16_to_cpu(vrh, used->flags), +				   vringh16_to_cpu(vrh, used->idx)); +			for (j = 0; j < num; j++) +				seq_printf(s, "used ring[%d] id %d len %d\n", +					   j, vringh32_to_cpu(vrh, +							      used->ring[j].id), +					   vringh32_to_cpu(vrh, +							   used->ring[j].len)); +		} +	} +	mutex_unlock(&vi->vop_mutex); + +	return 0; +} + +static int vop_vdev_info_debug_open(struct inode *inode, struct file *file) +{ +	return single_open(file, vop_vdev_info_show, inode->i_private); +} + +static int vop_vdev_info_debug_release(struct inode *inode, struct file *file) +{ +	return single_release(inode, file); +} + +static const struct file_operations vdev_info_ops = { +	.owner   = THIS_MODULE, +	.open    = vop_vdev_info_debug_open, +	.read    = seq_read, +	.llseek  = seq_lseek, +	.release = vop_vdev_info_debug_release +}; + +void vop_init_debugfs(struct vop_info *vi) +{ +	char name[16]; + +	snprintf(name, sizeof(name), "%s%d", KBUILD_MODNAME, vi->vpdev->dnode); +	vi->dbg = debugfs_create_dir(name, NULL); +	if (!vi->dbg) { +		pr_err("can't create debugfs dir vop\n"); +		return; +	} +	debugfs_create_file("dp", 0444, vi->dbg, vi, &dp_ops); +	debugfs_create_file("vdev_info", 0444, vi->dbg, vi, &vdev_info_ops); +} + +void vop_exit_debugfs(struct vop_info *vi) +{ +	debugfs_remove_recursive(vi->dbg); +} diff --git a/drivers/misc/mic/vop/vop_main.c b/drivers/misc/mic/vop/vop_main.c new file mode 100644 index 000000000000..1a2b67f3183d --- /dev/null +++ b/drivers/misc/mic/vop/vop_main.c @@ -0,0 +1,755 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Adapted from: + * + * virtio for kvm on s390 + * + * Copyright IBM Corp. 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2 only) + * as published by the Free Software Foundation. + * + *    Author(s): Christian Borntraeger <borntraeger@de.ibm.com> + * + * Intel Virtio Over PCIe (VOP) driver. + * + */ +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/dma-mapping.h> + +#include "vop_main.h" + +#define VOP_MAX_VRINGS 4 + +/* + * _vop_vdev - Allocated per virtio device instance injected by the peer. + * + * @vdev: Virtio device + * @desc: Virtio device page descriptor + * @dc: Virtio device control + * @vpdev: VOP device which is the parent for this virtio device + * @vr: Buffer for accessing the VRING + * @used: Buffer for used + * @used_size: Size of the used buffer + * @reset_done: Track whether VOP reset is complete + * @virtio_cookie: Cookie returned upon requesting a interrupt + * @c2h_vdev_db: The doorbell used by the guest to interrupt the host + * @h2c_vdev_db: The doorbell used by the host to interrupt the guest + * @dnode: The destination node + */ +struct _vop_vdev { +	struct virtio_device vdev; +	struct mic_device_desc __iomem *desc; +	struct mic_device_ctrl __iomem *dc; +	struct vop_device *vpdev; +	void __iomem *vr[VOP_MAX_VRINGS]; +	dma_addr_t used[VOP_MAX_VRINGS]; +	int used_size[VOP_MAX_VRINGS]; +	struct completion reset_done; +	struct mic_irq *virtio_cookie; +	int c2h_vdev_db; +	int h2c_vdev_db; +	int dnode; +}; + +#define to_vopvdev(vd) container_of(vd, struct _vop_vdev, vdev) + +#define _vop_aligned_desc_size(d) __mic_align(_vop_desc_size(d), 8) + +/* Helper API to obtain the parent of the virtio device */ +static inline struct device *_vop_dev(struct _vop_vdev *vdev) +{ +	return vdev->vdev.dev.parent; +} + +static inline unsigned _vop_desc_size(struct mic_device_desc __iomem *desc) +{ +	return sizeof(*desc) +		+ ioread8(&desc->num_vq) * sizeof(struct mic_vqconfig) +		+ ioread8(&desc->feature_len) * 2 +		+ ioread8(&desc->config_len); +} + +static inline struct mic_vqconfig __iomem * +_vop_vq_config(struct mic_device_desc __iomem *desc) +{ +	return (struct mic_vqconfig __iomem *)(desc + 1); +} + +static inline u8 __iomem * +_vop_vq_features(struct mic_device_desc __iomem *desc) +{ +	return (u8 __iomem *)(_vop_vq_config(desc) + ioread8(&desc->num_vq)); +} + +static inline u8 __iomem * +_vop_vq_configspace(struct mic_device_desc __iomem *desc) +{ +	return _vop_vq_features(desc) + ioread8(&desc->feature_len) * 2; +} + +static inline unsigned +_vop_total_desc_size(struct mic_device_desc __iomem *desc) +{ +	return _vop_aligned_desc_size(desc) + sizeof(struct mic_device_ctrl); +} + +/* This gets the device's feature bits. */ +static u64 vop_get_features(struct virtio_device *vdev) +{ +	unsigned int i, bits; +	u32 features = 0; +	struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc; +	u8 __iomem *in_features = _vop_vq_features(desc); +	int feature_len = ioread8(&desc->feature_len); + +	bits = min_t(unsigned, feature_len, sizeof(vdev->features)) * 8; +	for (i = 0; i < bits; i++) +		if (ioread8(&in_features[i / 8]) & (BIT(i % 8))) +			features |= BIT(i); + +	return features; +} + +static int vop_finalize_features(struct virtio_device *vdev) +{ +	unsigned int i, bits; +	struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc; +	u8 feature_len = ioread8(&desc->feature_len); +	/* Second half of bitmap is features we accept. */ +	u8 __iomem *out_features = +		_vop_vq_features(desc) + feature_len; + +	/* Give virtio_ring a chance to accept features. */ +	vring_transport_features(vdev); + +	memset_io(out_features, 0, feature_len); +	bits = min_t(unsigned, feature_len, +		     sizeof(vdev->features)) * 8; +	for (i = 0; i < bits; i++) { +		if (__virtio_test_bit(vdev, i)) +			iowrite8(ioread8(&out_features[i / 8]) | (1 << (i % 8)), +				 &out_features[i / 8]); +	} +	return 0; +} + +/* + * Reading and writing elements in config space + */ +static void vop_get(struct virtio_device *vdev, unsigned int offset, +		    void *buf, unsigned len) +{ +	struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc; + +	if (offset + len > ioread8(&desc->config_len)) +		return; +	memcpy_fromio(buf, _vop_vq_configspace(desc) + offset, len); +} + +static void vop_set(struct virtio_device *vdev, unsigned int offset, +		    const void *buf, unsigned len) +{ +	struct mic_device_desc __iomem *desc = to_vopvdev(vdev)->desc; + +	if (offset + len > ioread8(&desc->config_len)) +		return; +	memcpy_toio(_vop_vq_configspace(desc) + offset, buf, len); +} + +/* + * The operations to get and set the status word just access the status + * field of the device descriptor. set_status also interrupts the host + * to tell about status changes. + */ +static u8 vop_get_status(struct virtio_device *vdev) +{ +	return ioread8(&to_vopvdev(vdev)->desc->status); +} + +static void vop_set_status(struct virtio_device *dev, u8 status) +{ +	struct _vop_vdev *vdev = to_vopvdev(dev); +	struct vop_device *vpdev = vdev->vpdev; + +	if (!status) +		return; +	iowrite8(status, &vdev->desc->status); +	vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db); +} + +/* Inform host on a virtio device reset and wait for ack from host */ +static void vop_reset_inform_host(struct virtio_device *dev) +{ +	struct _vop_vdev *vdev = to_vopvdev(dev); +	struct mic_device_ctrl __iomem *dc = vdev->dc; +	struct vop_device *vpdev = vdev->vpdev; +	int retry; + +	iowrite8(0, &dc->host_ack); +	iowrite8(1, &dc->vdev_reset); +	vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db); + +	/* Wait till host completes all card accesses and acks the reset */ +	for (retry = 100; retry--;) { +		if (ioread8(&dc->host_ack)) +			break; +		msleep(100); +	}; + +	dev_dbg(_vop_dev(vdev), "%s: retry: %d\n", __func__, retry); + +	/* Reset status to 0 in case we timed out */ +	iowrite8(0, &vdev->desc->status); +} + +static void vop_reset(struct virtio_device *dev) +{ +	struct _vop_vdev *vdev = to_vopvdev(dev); + +	dev_dbg(_vop_dev(vdev), "%s: virtio id %d\n", +		__func__, dev->id.device); + +	vop_reset_inform_host(dev); +	complete_all(&vdev->reset_done); +} + +/* + * The virtio_ring code calls this API when it wants to notify the Host. + */ +static bool vop_notify(struct virtqueue *vq) +{ +	struct _vop_vdev *vdev = vq->priv; +	struct vop_device *vpdev = vdev->vpdev; + +	vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db); +	return true; +} + +static void vop_del_vq(struct virtqueue *vq, int n) +{ +	struct _vop_vdev *vdev = to_vopvdev(vq->vdev); +	struct vring *vr = (struct vring *)(vq + 1); +	struct vop_device *vpdev = vdev->vpdev; + +	dma_unmap_single(&vpdev->dev, vdev->used[n], +			 vdev->used_size[n], DMA_BIDIRECTIONAL); +	free_pages((unsigned long)vr->used, get_order(vdev->used_size[n])); +	vring_del_virtqueue(vq); +	vpdev->hw_ops->iounmap(vpdev, vdev->vr[n]); +	vdev->vr[n] = NULL; +} + +static void vop_del_vqs(struct virtio_device *dev) +{ +	struct _vop_vdev *vdev = to_vopvdev(dev); +	struct virtqueue *vq, *n; +	int idx = 0; + +	dev_dbg(_vop_dev(vdev), "%s\n", __func__); + +	list_for_each_entry_safe(vq, n, &dev->vqs, list) +		vop_del_vq(vq, idx++); +} + +/* + * This routine will assign vring's allocated in host/io memory. Code in + * virtio_ring.c however continues to access this io memory as if it were local + * memory without io accessors. + */ +static struct virtqueue *vop_find_vq(struct virtio_device *dev, +				     unsigned index, +				     void (*callback)(struct virtqueue *vq), +				     const char *name) +{ +	struct _vop_vdev *vdev = to_vopvdev(dev); +	struct vop_device *vpdev = vdev->vpdev; +	struct mic_vqconfig __iomem *vqconfig; +	struct mic_vqconfig config; +	struct virtqueue *vq; +	void __iomem *va; +	struct _mic_vring_info __iomem *info; +	void *used; +	int vr_size, _vr_size, err, magic; +	struct vring *vr; +	u8 type = ioread8(&vdev->desc->type); + +	if (index >= ioread8(&vdev->desc->num_vq)) +		return ERR_PTR(-ENOENT); + +	if (!name) +		return ERR_PTR(-ENOENT); + +	/* First assign the vring's allocated in host memory */ +	vqconfig = _vop_vq_config(vdev->desc) + index; +	memcpy_fromio(&config, vqconfig, sizeof(config)); +	_vr_size = vring_size(le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN); +	vr_size = PAGE_ALIGN(_vr_size + sizeof(struct _mic_vring_info)); +	va = vpdev->hw_ops->ioremap(vpdev, le64_to_cpu(config.address), +			vr_size); +	if (!va) +		return ERR_PTR(-ENOMEM); +	vdev->vr[index] = va; +	memset_io(va, 0x0, _vr_size); +	vq = vring_new_virtqueue( +				index, +				le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN, +				dev, +				false, +				(void __force *)va, vop_notify, callback, name); +	if (!vq) { +		err = -ENOMEM; +		goto unmap; +	} +	info = va + _vr_size; +	magic = ioread32(&info->magic); + +	if (WARN(magic != MIC_MAGIC + type + index, "magic mismatch")) { +		err = -EIO; +		goto unmap; +	} + +	/* Allocate and reassign used ring now */ +	vdev->used_size[index] = PAGE_ALIGN(sizeof(__u16) * 3 + +					     sizeof(struct vring_used_elem) * +					     le16_to_cpu(config.num)); +	used = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, +					get_order(vdev->used_size[index])); +	if (!used) { +		err = -ENOMEM; +		dev_err(_vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, err); +		goto del_vq; +	} +	vdev->used[index] = dma_map_single(&vpdev->dev, used, +					    vdev->used_size[index], +					    DMA_BIDIRECTIONAL); +	if (dma_mapping_error(&vpdev->dev, vdev->used[index])) { +		err = -ENOMEM; +		dev_err(_vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, err); +		goto free_used; +	} +	writeq(vdev->used[index], &vqconfig->used_address); +	/* +	 * To reassign the used ring here we are directly accessing +	 * struct vring_virtqueue which is a private data structure +	 * in virtio_ring.c. At the minimum, a BUILD_BUG_ON() in +	 * vring_new_virtqueue() would ensure that +	 *  (&vq->vring == (struct vring *) (&vq->vq + 1)); +	 */ +	vr = (struct vring *)(vq + 1); +	vr->used = used; + +	vq->priv = vdev; +	return vq; +free_used: +	free_pages((unsigned long)used, +		   get_order(vdev->used_size[index])); +del_vq: +	vring_del_virtqueue(vq); +unmap: +	vpdev->hw_ops->iounmap(vpdev, vdev->vr[index]); +	return ERR_PTR(err); +} + +static int vop_find_vqs(struct virtio_device *dev, unsigned nvqs, +			struct virtqueue *vqs[], +			vq_callback_t *callbacks[], +			const char * const names[]) +{ +	struct _vop_vdev *vdev = to_vopvdev(dev); +	struct vop_device *vpdev = vdev->vpdev; +	struct mic_device_ctrl __iomem *dc = vdev->dc; +	int i, err, retry; + +	/* We must have this many virtqueues. */ +	if (nvqs > ioread8(&vdev->desc->num_vq)) +		return -ENOENT; + +	for (i = 0; i < nvqs; ++i) { +		dev_dbg(_vop_dev(vdev), "%s: %d: %s\n", +			__func__, i, names[i]); +		vqs[i] = vop_find_vq(dev, i, callbacks[i], names[i]); +		if (IS_ERR(vqs[i])) { +			err = PTR_ERR(vqs[i]); +			goto error; +		} +	} + +	iowrite8(1, &dc->used_address_updated); +	/* +	 * Send an interrupt to the host to inform it that used +	 * rings have been re-assigned. +	 */ +	vpdev->hw_ops->send_intr(vpdev, vdev->c2h_vdev_db); +	for (retry = 100; --retry;) { +		if (!ioread8(&dc->used_address_updated)) +			break; +		msleep(100); +	}; + +	dev_dbg(_vop_dev(vdev), "%s: retry: %d\n", __func__, retry); +	if (!retry) { +		err = -ENODEV; +		goto error; +	} + +	return 0; +error: +	vop_del_vqs(dev); +	return err; +} + +/* + * The config ops structure as defined by virtio config + */ +static struct virtio_config_ops vop_vq_config_ops = { +	.get_features = vop_get_features, +	.finalize_features = vop_finalize_features, +	.get = vop_get, +	.set = vop_set, +	.get_status = vop_get_status, +	.set_status = vop_set_status, +	.reset = vop_reset, +	.find_vqs = vop_find_vqs, +	.del_vqs = vop_del_vqs, +}; + +static irqreturn_t vop_virtio_intr_handler(int irq, void *data) +{ +	struct _vop_vdev *vdev = data; +	struct vop_device *vpdev = vdev->vpdev; +	struct virtqueue *vq; + +	vpdev->hw_ops->ack_interrupt(vpdev, vdev->h2c_vdev_db); +	list_for_each_entry(vq, &vdev->vdev.vqs, list) +		vring_interrupt(0, vq); + +	return IRQ_HANDLED; +} + +static void vop_virtio_release_dev(struct device *_d) +{ +	/* +	 * No need for a release method similar to virtio PCI. +	 * Provide an empty one to avoid getting a warning from core. +	 */ +} + +/* + * adds a new device and register it with virtio + * appropriate drivers are loaded by the device model + */ +static int _vop_add_device(struct mic_device_desc __iomem *d, +			   unsigned int offset, struct vop_device *vpdev, +			   int dnode) +{ +	struct _vop_vdev *vdev; +	int ret; +	u8 type = ioread8(&d->type); + +	vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); +	if (!vdev) +		return -ENOMEM; + +	vdev->vpdev = vpdev; +	vdev->vdev.dev.parent = &vpdev->dev; +	vdev->vdev.dev.release = vop_virtio_release_dev; +	vdev->vdev.id.device = type; +	vdev->vdev.config = &vop_vq_config_ops; +	vdev->desc = d; +	vdev->dc = (void __iomem *)d + _vop_aligned_desc_size(d); +	vdev->dnode = dnode; +	vdev->vdev.priv = (void *)(u64)dnode; +	init_completion(&vdev->reset_done); + +	vdev->h2c_vdev_db = vpdev->hw_ops->next_db(vpdev); +	vdev->virtio_cookie = vpdev->hw_ops->request_irq(vpdev, +			vop_virtio_intr_handler, "virtio intr", +			vdev, vdev->h2c_vdev_db); +	if (IS_ERR(vdev->virtio_cookie)) { +		ret = PTR_ERR(vdev->virtio_cookie); +		goto kfree; +	} +	iowrite8((u8)vdev->h2c_vdev_db, &vdev->dc->h2c_vdev_db); +	vdev->c2h_vdev_db = ioread8(&vdev->dc->c2h_vdev_db); + +	ret = register_virtio_device(&vdev->vdev); +	if (ret) { +		dev_err(_vop_dev(vdev), +			"Failed to register vop device %u type %u\n", +			offset, type); +		goto free_irq; +	} +	writeq((u64)vdev, &vdev->dc->vdev); +	dev_dbg(_vop_dev(vdev), "%s: registered vop device %u type %u vdev %p\n", +		__func__, offset, type, vdev); + +	return 0; + +free_irq: +	vpdev->hw_ops->free_irq(vpdev, vdev->virtio_cookie, vdev); +kfree: +	kfree(vdev); +	return ret; +} + +/* + * match for a vop device with a specific desc pointer + */ +static int vop_match_desc(struct device *dev, void *data) +{ +	struct virtio_device *_dev = dev_to_virtio(dev); +	struct _vop_vdev *vdev = to_vopvdev(_dev); + +	return vdev->desc == (void __iomem *)data; +} + +static void _vop_handle_config_change(struct mic_device_desc __iomem *d, +				      unsigned int offset, +				      struct vop_device *vpdev) +{ +	struct mic_device_ctrl __iomem *dc +		= (void __iomem *)d + _vop_aligned_desc_size(d); +	struct _vop_vdev *vdev = (struct _vop_vdev *)readq(&dc->vdev); + +	if (ioread8(&dc->config_change) != MIC_VIRTIO_PARAM_CONFIG_CHANGED) +		return; + +	dev_dbg(&vpdev->dev, "%s %d\n", __func__, __LINE__); +	virtio_config_changed(&vdev->vdev); +	iowrite8(1, &dc->guest_ack); +} + +/* + * removes a virtio device if a hot remove event has been + * requested by the host. + */ +static int _vop_remove_device(struct mic_device_desc __iomem *d, +			      unsigned int offset, struct vop_device *vpdev) +{ +	struct mic_device_ctrl __iomem *dc +		= (void __iomem *)d + _vop_aligned_desc_size(d); +	struct _vop_vdev *vdev = (struct _vop_vdev *)readq(&dc->vdev); +	u8 status; +	int ret = -1; + +	if (ioread8(&dc->config_change) == MIC_VIRTIO_PARAM_DEV_REMOVE) { +		dev_dbg(&vpdev->dev, +			"%s %d config_change %d type %d vdev %p\n", +			__func__, __LINE__, +			ioread8(&dc->config_change), ioread8(&d->type), vdev); +		status = ioread8(&d->status); +		reinit_completion(&vdev->reset_done); +		unregister_virtio_device(&vdev->vdev); +		vpdev->hw_ops->free_irq(vpdev, vdev->virtio_cookie, vdev); +		iowrite8(-1, &dc->h2c_vdev_db); +		if (status & VIRTIO_CONFIG_S_DRIVER_OK) +			wait_for_completion(&vdev->reset_done); +		kfree(vdev); +		iowrite8(1, &dc->guest_ack); +		dev_dbg(&vpdev->dev, "%s %d guest_ack %d\n", +			__func__, __LINE__, ioread8(&dc->guest_ack)); +		iowrite8(-1, &d->type); +		ret = 0; +	} +	return ret; +} + +#define REMOVE_DEVICES true + +static void _vop_scan_devices(void __iomem *dp, struct vop_device *vpdev, +			      bool remove, int dnode) +{ +	s8 type; +	unsigned int i; +	struct mic_device_desc __iomem *d; +	struct mic_device_ctrl __iomem *dc; +	struct device *dev; +	int ret; + +	for (i = sizeof(struct mic_bootparam); +			i < MIC_DP_SIZE; i += _vop_total_desc_size(d)) { +		d = dp + i; +		dc = (void __iomem *)d + _vop_aligned_desc_size(d); +		/* +		 * This read barrier is paired with the corresponding write +		 * barrier on the host which is inserted before adding or +		 * removing a virtio device descriptor, by updating the type. +		 */ +		rmb(); +		type = ioread8(&d->type); + +		/* end of list */ +		if (type == 0) +			break; + +		if (type == -1) +			continue; + +		/* device already exists */ +		dev = device_find_child(&vpdev->dev, (void __force *)d, +					vop_match_desc); +		if (dev) { +			if (remove) +				iowrite8(MIC_VIRTIO_PARAM_DEV_REMOVE, +					 &dc->config_change); +			put_device(dev); +			_vop_handle_config_change(d, i, vpdev); +			ret = _vop_remove_device(d, i, vpdev); +			if (remove) { +				iowrite8(0, &dc->config_change); +				iowrite8(0, &dc->guest_ack); +			} +			continue; +		} + +		/* new device */ +		dev_dbg(&vpdev->dev, "%s %d Adding new virtio device %p\n", +			__func__, __LINE__, d); +		if (!remove) +			_vop_add_device(d, i, vpdev, dnode); +	} +} + +static void vop_scan_devices(struct vop_info *vi, +			     struct vop_device *vpdev, bool remove) +{ +	void __iomem *dp = vpdev->hw_ops->get_remote_dp(vpdev); + +	if (!dp) +		return; +	mutex_lock(&vi->vop_mutex); +	_vop_scan_devices(dp, vpdev, remove, vpdev->dnode); +	mutex_unlock(&vi->vop_mutex); +} + +/* + * vop_hotplug_device tries to find changes in the device page. + */ +static void vop_hotplug_devices(struct work_struct *work) +{ +	struct vop_info *vi = container_of(work, struct vop_info, +					     hotplug_work); + +	vop_scan_devices(vi, vi->vpdev, !REMOVE_DEVICES); +} + +/* + * Interrupt handler for hot plug/config changes etc. + */ +static irqreturn_t vop_extint_handler(int irq, void *data) +{ +	struct vop_info *vi = data; +	struct mic_bootparam __iomem *bp; +	struct vop_device *vpdev = vi->vpdev; + +	bp = vpdev->hw_ops->get_remote_dp(vpdev); +	dev_dbg(&vpdev->dev, "%s %d hotplug work\n", +		__func__, __LINE__); +	vpdev->hw_ops->ack_interrupt(vpdev, ioread8(&bp->h2c_config_db)); +	schedule_work(&vi->hotplug_work); +	return IRQ_HANDLED; +} + +static int vop_driver_probe(struct vop_device *vpdev) +{ +	struct vop_info *vi; +	int rc; + +	vi = kzalloc(sizeof(*vi), GFP_KERNEL); +	if (!vi) { +		rc = -ENOMEM; +		goto exit; +	} +	dev_set_drvdata(&vpdev->dev, vi); +	vi->vpdev = vpdev; + +	mutex_init(&vi->vop_mutex); +	INIT_WORK(&vi->hotplug_work, vop_hotplug_devices); +	if (vpdev->dnode) { +		rc = vop_host_init(vi); +		if (rc < 0) +			goto free; +	} else { +		struct mic_bootparam __iomem *bootparam; + +		vop_scan_devices(vi, vpdev, !REMOVE_DEVICES); + +		vi->h2c_config_db = vpdev->hw_ops->next_db(vpdev); +		vi->cookie = vpdev->hw_ops->request_irq(vpdev, +							vop_extint_handler, +							"virtio_config_intr", +							vi, vi->h2c_config_db); +		if (IS_ERR(vi->cookie)) { +			rc = PTR_ERR(vi->cookie); +			goto free; +		} +		bootparam = vpdev->hw_ops->get_remote_dp(vpdev); +		iowrite8(vi->h2c_config_db, &bootparam->h2c_config_db); +	} +	vop_init_debugfs(vi); +	return 0; +free: +	kfree(vi); +exit: +	return rc; +} + +static void vop_driver_remove(struct vop_device *vpdev) +{ +	struct vop_info *vi = dev_get_drvdata(&vpdev->dev); + +	if (vpdev->dnode) { +		vop_host_uninit(vi); +	} else { +		struct mic_bootparam __iomem *bootparam = +			vpdev->hw_ops->get_remote_dp(vpdev); +		if (bootparam) +			iowrite8(-1, &bootparam->h2c_config_db); +		vpdev->hw_ops->free_irq(vpdev, vi->cookie, vi); +		flush_work(&vi->hotplug_work); +		vop_scan_devices(vi, vpdev, REMOVE_DEVICES); +	} +	vop_exit_debugfs(vi); +	kfree(vi); +} + +static struct vop_device_id id_table[] = { +	{ VOP_DEV_TRNSP, VOP_DEV_ANY_ID }, +	{ 0 }, +}; + +static struct vop_driver vop_driver = { +	.driver.name =	KBUILD_MODNAME, +	.driver.owner =	THIS_MODULE, +	.id_table = id_table, +	.probe = vop_driver_probe, +	.remove = vop_driver_remove, +}; + +module_vop_driver(vop_driver); + +MODULE_DEVICE_TABLE(mbus, id_table); +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) Virtio Over PCIe (VOP) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mic/host/mic_virtio.h b/drivers/misc/mic/vop/vop_main.h index a80631f2790d..ba47ec7a6386 100644 --- a/drivers/misc/mic/host/mic_virtio.h +++ b/drivers/misc/mic/vop/vop_main.h @@ -1,7 +1,7 @@  /*   * Intel MIC Platform Software Stack (MPSS)   * - * Copyright(c) 2013 Intel Corporation. + * Copyright(c) 2016 Intel Corporation.   *   * This program is free software; you can redistribute it and/or modify   * it under the terms of the GNU General Public License, version 2, as @@ -15,14 +15,21 @@   * The full GNU General Public License is included in this distribution in   * the file called "COPYING".   * - * Intel MIC Host driver. + * Intel Virtio Over PCIe (VOP) driver.   *   */ -#ifndef MIC_VIRTIO_H -#define MIC_VIRTIO_H +#ifndef _VOP_MAIN_H_ +#define _VOP_MAIN_H_ +#include <linux/vringh.h>  #include <linux/virtio_config.h> -#include <linux/mic_ioctl.h> +#include <linux/virtio.h> +#include <linux/miscdevice.h> + +#include <linux/mic_common.h> +#include "../common/mic_dev.h" + +#include "../bus/vop_bus.h"  /*   * Note on endianness. @@ -39,38 +46,68 @@   *    in guest endianness.   */ +/* + * vop_info - Allocated per invocation of VOP probe + * + * @vpdev: VOP device + * @hotplug_work: Handle virtio device creation, deletion and configuration + * @cookie: Cookie received upon requesting a virtio configuration interrupt + * @h2c_config_db: The doorbell used by the peer to indicate a config change + * @vdev_list: List of "active" virtio devices injected in the peer node + * @vop_mutex: Synchronize access to the device page as well as serialize + *             creation/deletion of virtio devices on the peer node + * @dp: Peer device page information + * @dbg: Debugfs entry + * @dma_ch: The DMA channel used by this transport for data transfers. + * @name: Name for this transport used in misc device creation. + * @miscdev: The misc device registered. + */ +struct vop_info { +	struct vop_device *vpdev; +	struct work_struct hotplug_work; +	struct mic_irq *cookie; +	int h2c_config_db; +	struct list_head vdev_list; +	struct mutex vop_mutex; +	void __iomem *dp; +	struct dentry *dbg; +	struct dma_chan *dma_ch; +	char name[16]; +	struct miscdevice miscdev; +}; +  /** - * struct mic_vringh - Virtio ring host information. + * struct vop_vringh - Virtio ring host information.   * - * @vring: The MIC vring used for setting up user space mappings. + * @vring: The VOP vring used for setting up user space mappings.   * @vrh: The host VRINGH used for accessing the card vrings.   * @riov: The VRINGH read kernel IOV.   * @wiov: The VRINGH write kernel IOV. + * @head: The VRINGH head index address passed to vringh_getdesc_kern(..).   * @vr_mutex: Mutex for synchronizing access to the VRING.   * @buf: Temporary kernel buffer used to copy in/out data   * from/to the card via DMA.   * @buf_da: dma address of buf. - * @mvdev: Back pointer to MIC virtio device for vringh_notify(..). - * @head: The VRINGH head index address passed to vringh_getdesc_kern(..). + * @vdev: Back pointer to VOP virtio device for vringh_notify(..).   */ -struct mic_vringh { +struct vop_vringh {  	struct mic_vring vring;  	struct vringh vrh;  	struct vringh_kiov riov;  	struct vringh_kiov wiov; +	u16 head;  	struct mutex vr_mutex;  	void *buf;  	dma_addr_t buf_da; -	struct mic_vdev *mvdev; -	u16 head; +	struct vop_vdev *vdev;  };  /** - * struct mic_vdev - Host information for a card Virtio device. + * struct vop_vdev - Host information for a card Virtio device.   *   * @virtio_id - Virtio device id.   * @waitq - Waitqueue to allow ring3 apps to poll. - * @mdev - Back pointer to host MIC device. + * @vpdev - pointer to VOP bus device.   * @poll_wake - Used for waking up threads blocked in poll.   * @out_bytes - Debug stats for number of bytes copied from host to card.   * @in_bytes - Debug stats for number of bytes copied from card to host. @@ -82,18 +119,23 @@ struct mic_vringh {   * the transfer length did not have the required DMA alignment.   * @tx_dst_unaligned - Debug stats for number of bytes copied where the   * destination address on the card did not have the required DMA alignment. - * @mvr - Store per VRING data structures. + * @vvr - Store per VRING data structures.   * @virtio_bh_work - Work struct used to schedule virtio bottom half handling.   * @dd - Virtio device descriptor.   * @dc - Virtio device control fields.   * @list - List of Virtio devices.   * @virtio_db - The doorbell used by the card to interrupt the host.   * @virtio_cookie - The cookie returned while requesting interrupts. + * @vi: Transport information. + * @vdev_mutex: Mutex synchronizing virtio device injection, + *              removal and data transfers. + * @destroy: Track if a virtio device is being destroyed. + * @deleted: The virtio device has been deleted.   */ -struct mic_vdev { +struct vop_vdev {  	int virtio_id;  	wait_queue_head_t waitq; -	struct mic_device *mdev; +	struct vop_device *vpdev;  	int poll_wake;  	unsigned long out_bytes;  	unsigned long in_bytes; @@ -101,55 +143,28 @@ struct mic_vdev {  	unsigned long in_bytes_dma;  	unsigned long tx_len_unaligned;  	unsigned long tx_dst_unaligned; -	struct mic_vringh mvr[MIC_MAX_VRINGS]; +	unsigned long rx_dst_unaligned; +	struct vop_vringh vvr[MIC_MAX_VRINGS];  	struct work_struct virtio_bh_work;  	struct mic_device_desc *dd;  	struct mic_device_ctrl *dc;  	struct list_head list;  	int virtio_db;  	struct mic_irq *virtio_cookie; +	struct vop_info *vi; +	struct mutex vdev_mutex; +	struct completion destroy; +	bool deleted;  }; -void mic_virtio_uninit(struct mic_device *mdev); -int mic_virtio_add_device(struct mic_vdev *mvdev, -			void __user *argp); -void mic_virtio_del_device(struct mic_vdev *mvdev); -int mic_virtio_config_change(struct mic_vdev *mvdev, -			void __user *argp); -int mic_virtio_copy_desc(struct mic_vdev *mvdev, -	struct mic_copy_desc *request); -void mic_virtio_reset_devices(struct mic_device *mdev); -void mic_bh_handler(struct work_struct *work); - -/* Helper API to obtain the MIC PCIe device */ -static inline struct device *mic_dev(struct mic_vdev *mvdev) -{ -	return &mvdev->mdev->pdev->dev; -} - -/* Helper API to check if a virtio device is initialized */ -static inline int mic_vdev_inited(struct mic_vdev *mvdev) -{ -	/* Device has not been created yet */ -	if (!mvdev->dd || !mvdev->dd->type) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, -EINVAL); -		return -EINVAL; -	} - -	/* Device has been removed/deleted */ -	if (mvdev->dd->type == -1) { -		dev_err(mic_dev(mvdev), "%s %d err %d\n", -			__func__, __LINE__, -ENODEV); -		return -ENODEV; -	} - -	return 0; -} -  /* Helper API to check if a virtio device is running */ -static inline bool mic_vdevup(struct mic_vdev *mvdev) +static inline bool vop_vdevup(struct vop_vdev *vdev)  { -	return !!mvdev->dd->status; +	return !!vdev->dd->status;  } + +void vop_init_debugfs(struct vop_info *vi); +void vop_exit_debugfs(struct vop_info *vi); +int vop_host_init(struct vop_info *vi); +void vop_host_uninit(struct vop_info *vi);  #endif diff --git a/drivers/misc/mic/vop/vop_vringh.c b/drivers/misc/mic/vop/vop_vringh.c new file mode 100644 index 000000000000..e94c7fb6712a --- /dev/null +++ b/drivers/misc/mic/vop/vop_vringh.c @@ -0,0 +1,1165 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel Virtio Over PCIe (VOP) driver. + * + */ +#include <linux/sched.h> +#include <linux/poll.h> +#include <linux/dma-mapping.h> + +#include <linux/mic_common.h> +#include "../common/mic_dev.h" + +#include <linux/mic_ioctl.h> +#include "vop_main.h" + +/* Helper API to obtain the VOP PCIe device */ +static inline struct device *vop_dev(struct vop_vdev *vdev) +{ +	return vdev->vpdev->dev.parent; +} + +/* Helper API to check if a virtio device is initialized */ +static inline int vop_vdev_inited(struct vop_vdev *vdev) +{ +	if (!vdev) +		return -EINVAL; +	/* Device has not been created yet */ +	if (!vdev->dd || !vdev->dd->type) { +		dev_err(vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, -EINVAL); +		return -EINVAL; +	} +	/* Device has been removed/deleted */ +	if (vdev->dd->type == -1) { +		dev_dbg(vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, -ENODEV); +		return -ENODEV; +	} +	return 0; +} + +static void _vop_notify(struct vringh *vrh) +{ +	struct vop_vringh *vvrh = container_of(vrh, struct vop_vringh, vrh); +	struct vop_vdev *vdev = vvrh->vdev; +	struct vop_device *vpdev = vdev->vpdev; +	s8 db = vdev->dc->h2c_vdev_db; + +	if (db != -1) +		vpdev->hw_ops->send_intr(vpdev, db); +} + +static void vop_virtio_init_post(struct vop_vdev *vdev) +{ +	struct mic_vqconfig *vqconfig = mic_vq_config(vdev->dd); +	struct vop_device *vpdev = vdev->vpdev; +	int i, used_size; + +	for (i = 0; i < vdev->dd->num_vq; i++) { +		used_size = PAGE_ALIGN(sizeof(u16) * 3 + +				sizeof(struct vring_used_elem) * +				le16_to_cpu(vqconfig->num)); +		if (!le64_to_cpu(vqconfig[i].used_address)) { +			dev_warn(vop_dev(vdev), "used_address zero??\n"); +			continue; +		} +		vdev->vvr[i].vrh.vring.used = +			(void __force *)vpdev->hw_ops->ioremap( +			vpdev, +			le64_to_cpu(vqconfig[i].used_address), +			used_size); +	} + +	vdev->dc->used_address_updated = 0; + +	dev_info(vop_dev(vdev), "%s: device type %d LINKUP\n", +		 __func__, vdev->virtio_id); +} + +static inline void vop_virtio_device_reset(struct vop_vdev *vdev) +{ +	int i; + +	dev_dbg(vop_dev(vdev), "%s: status %d device type %d RESET\n", +		__func__, vdev->dd->status, vdev->virtio_id); + +	for (i = 0; i < vdev->dd->num_vq; i++) +		/* +		 * Avoid lockdep false positive. The + 1 is for the vop +		 * mutex which is held in the reset devices code path. +		 */ +		mutex_lock_nested(&vdev->vvr[i].vr_mutex, i + 1); + +	/* 0 status means "reset" */ +	vdev->dd->status = 0; +	vdev->dc->vdev_reset = 0; +	vdev->dc->host_ack = 1; + +	for (i = 0; i < vdev->dd->num_vq; i++) { +		struct vringh *vrh = &vdev->vvr[i].vrh; + +		vdev->vvr[i].vring.info->avail_idx = 0; +		vrh->completed = 0; +		vrh->last_avail_idx = 0; +		vrh->last_used_idx = 0; +	} + +	for (i = 0; i < vdev->dd->num_vq; i++) +		mutex_unlock(&vdev->vvr[i].vr_mutex); +} + +static void vop_virtio_reset_devices(struct vop_info *vi) +{ +	struct list_head *pos, *tmp; +	struct vop_vdev *vdev; + +	list_for_each_safe(pos, tmp, &vi->vdev_list) { +		vdev = list_entry(pos, struct vop_vdev, list); +		vop_virtio_device_reset(vdev); +		vdev->poll_wake = 1; +		wake_up(&vdev->waitq); +	} +} + +static void vop_bh_handler(struct work_struct *work) +{ +	struct vop_vdev *vdev = container_of(work, struct vop_vdev, +			virtio_bh_work); + +	if (vdev->dc->used_address_updated) +		vop_virtio_init_post(vdev); + +	if (vdev->dc->vdev_reset) +		vop_virtio_device_reset(vdev); + +	vdev->poll_wake = 1; +	wake_up(&vdev->waitq); +} + +static irqreturn_t _vop_virtio_intr_handler(int irq, void *data) +{ +	struct vop_vdev *vdev = data; +	struct vop_device *vpdev = vdev->vpdev; + +	vpdev->hw_ops->ack_interrupt(vpdev, vdev->virtio_db); +	schedule_work(&vdev->virtio_bh_work); +	return IRQ_HANDLED; +} + +static int vop_virtio_config_change(struct vop_vdev *vdev, void *argp) +{ +	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake); +	int ret = 0, retry, i; +	struct vop_device *vpdev = vdev->vpdev; +	struct vop_info *vi = dev_get_drvdata(&vpdev->dev); +	struct mic_bootparam *bootparam = vpdev->hw_ops->get_dp(vpdev); +	s8 db = bootparam->h2c_config_db; + +	mutex_lock(&vi->vop_mutex); +	for (i = 0; i < vdev->dd->num_vq; i++) +		mutex_lock_nested(&vdev->vvr[i].vr_mutex, i + 1); + +	if (db == -1 || vdev->dd->type == -1) { +		ret = -EIO; +		goto exit; +	} + +	memcpy(mic_vq_configspace(vdev->dd), argp, vdev->dd->config_len); +	vdev->dc->config_change = MIC_VIRTIO_PARAM_CONFIG_CHANGED; +	vpdev->hw_ops->send_intr(vpdev, db); + +	for (retry = 100; retry--;) { +		ret = wait_event_timeout(wake, vdev->dc->guest_ack, +					 msecs_to_jiffies(100)); +		if (ret) +			break; +	} + +	dev_dbg(vop_dev(vdev), +		"%s %d retry: %d\n", __func__, __LINE__, retry); +	vdev->dc->config_change = 0; +	vdev->dc->guest_ack = 0; +exit: +	for (i = 0; i < vdev->dd->num_vq; i++) +		mutex_unlock(&vdev->vvr[i].vr_mutex); +	mutex_unlock(&vi->vop_mutex); +	return ret; +} + +static int vop_copy_dp_entry(struct vop_vdev *vdev, +			     struct mic_device_desc *argp, __u8 *type, +			     struct mic_device_desc **devpage) +{ +	struct vop_device *vpdev = vdev->vpdev; +	struct mic_device_desc *devp; +	struct mic_vqconfig *vqconfig; +	int ret = 0, i; +	bool slot_found = false; + +	vqconfig = mic_vq_config(argp); +	for (i = 0; i < argp->num_vq; i++) { +		if (le16_to_cpu(vqconfig[i].num) > MIC_MAX_VRING_ENTRIES) { +			ret =  -EINVAL; +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, ret); +			goto exit; +		} +	} + +	/* Find the first free device page entry */ +	for (i = sizeof(struct mic_bootparam); +		i < MIC_DP_SIZE - mic_total_desc_size(argp); +		i += mic_total_desc_size(devp)) { +		devp = vpdev->hw_ops->get_dp(vpdev) + i; +		if (devp->type == 0 || devp->type == -1) { +			slot_found = true; +			break; +		} +	} +	if (!slot_found) { +		ret =  -EINVAL; +		dev_err(vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, ret); +		goto exit; +	} +	/* +	 * Save off the type before doing the memcpy. Type will be set in the +	 * end after completing all initialization for the new device. +	 */ +	*type = argp->type; +	argp->type = 0; +	memcpy(devp, argp, mic_desc_size(argp)); + +	*devpage = devp; +exit: +	return ret; +} + +static void vop_init_device_ctrl(struct vop_vdev *vdev, +				 struct mic_device_desc *devpage) +{ +	struct mic_device_ctrl *dc; + +	dc = (void *)devpage + mic_aligned_desc_size(devpage); + +	dc->config_change = 0; +	dc->guest_ack = 0; +	dc->vdev_reset = 0; +	dc->host_ack = 0; +	dc->used_address_updated = 0; +	dc->c2h_vdev_db = -1; +	dc->h2c_vdev_db = -1; +	vdev->dc = dc; +} + +static int vop_virtio_add_device(struct vop_vdev *vdev, +				 struct mic_device_desc *argp) +{ +	struct vop_info *vi = vdev->vi; +	struct vop_device *vpdev = vi->vpdev; +	struct mic_device_desc *dd = NULL; +	struct mic_vqconfig *vqconfig; +	int vr_size, i, j, ret; +	u8 type = 0; +	s8 db = -1; +	char irqname[16]; +	struct mic_bootparam *bootparam; +	u16 num; +	dma_addr_t vr_addr; + +	bootparam = vpdev->hw_ops->get_dp(vpdev); +	init_waitqueue_head(&vdev->waitq); +	INIT_LIST_HEAD(&vdev->list); +	vdev->vpdev = vpdev; + +	ret = vop_copy_dp_entry(vdev, argp, &type, &dd); +	if (ret) { +		dev_err(vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, ret); +		kfree(vdev); +		return ret; +	} + +	vop_init_device_ctrl(vdev, dd); + +	vdev->dd = dd; +	vdev->virtio_id = type; +	vqconfig = mic_vq_config(dd); +	INIT_WORK(&vdev->virtio_bh_work, vop_bh_handler); + +	for (i = 0; i < dd->num_vq; i++) { +		struct vop_vringh *vvr = &vdev->vvr[i]; +		struct mic_vring *vr = &vdev->vvr[i].vring; + +		num = le16_to_cpu(vqconfig[i].num); +		mutex_init(&vvr->vr_mutex); +		vr_size = PAGE_ALIGN(vring_size(num, MIC_VIRTIO_RING_ALIGN) + +			sizeof(struct _mic_vring_info)); +		vr->va = (void *) +			__get_free_pages(GFP_KERNEL | __GFP_ZERO, +					 get_order(vr_size)); +		if (!vr->va) { +			ret = -ENOMEM; +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, ret); +			goto err; +		} +		vr->len = vr_size; +		vr->info = vr->va + vring_size(num, MIC_VIRTIO_RING_ALIGN); +		vr->info->magic = cpu_to_le32(MIC_MAGIC + vdev->virtio_id + i); +		vr_addr = dma_map_single(&vpdev->dev, vr->va, vr_size, +					 DMA_BIDIRECTIONAL); +		if (dma_mapping_error(&vpdev->dev, vr_addr)) { +			free_pages((unsigned long)vr->va, get_order(vr_size)); +			ret = -ENOMEM; +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, ret); +			goto err; +		} +		vqconfig[i].address = cpu_to_le64(vr_addr); + +		vring_init(&vr->vr, num, vr->va, MIC_VIRTIO_RING_ALIGN); +		ret = vringh_init_kern(&vvr->vrh, +				       *(u32 *)mic_vq_features(vdev->dd), +				       num, false, vr->vr.desc, vr->vr.avail, +				       vr->vr.used); +		if (ret) { +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, ret); +			goto err; +		} +		vringh_kiov_init(&vvr->riov, NULL, 0); +		vringh_kiov_init(&vvr->wiov, NULL, 0); +		vvr->head = USHRT_MAX; +		vvr->vdev = vdev; +		vvr->vrh.notify = _vop_notify; +		dev_dbg(&vpdev->dev, +			"%s %d index %d va %p info %p vr_size 0x%x\n", +			__func__, __LINE__, i, vr->va, vr->info, vr_size); +		vvr->buf = (void *)__get_free_pages(GFP_KERNEL, +					get_order(VOP_INT_DMA_BUF_SIZE)); +		vvr->buf_da = dma_map_single(&vpdev->dev, +					  vvr->buf, VOP_INT_DMA_BUF_SIZE, +					  DMA_BIDIRECTIONAL); +	} + +	snprintf(irqname, sizeof(irqname), "vop%dvirtio%d", vpdev->index, +		 vdev->virtio_id); +	vdev->virtio_db = vpdev->hw_ops->next_db(vpdev); +	vdev->virtio_cookie = vpdev->hw_ops->request_irq(vpdev, +			_vop_virtio_intr_handler, irqname, vdev, +			vdev->virtio_db); +	if (IS_ERR(vdev->virtio_cookie)) { +		ret = PTR_ERR(vdev->virtio_cookie); +		dev_dbg(&vpdev->dev, "request irq failed\n"); +		goto err; +	} + +	vdev->dc->c2h_vdev_db = vdev->virtio_db; + +	/* +	 * Order the type update with previous stores. This write barrier +	 * is paired with the corresponding read barrier before the uncached +	 * system memory read of the type, on the card while scanning the +	 * device page. +	 */ +	smp_wmb(); +	dd->type = type; +	argp->type = type; + +	if (bootparam) { +		db = bootparam->h2c_config_db; +		if (db != -1) +			vpdev->hw_ops->send_intr(vpdev, db); +	} +	dev_dbg(&vpdev->dev, "Added virtio id %d db %d\n", dd->type, db); +	return 0; +err: +	vqconfig = mic_vq_config(dd); +	for (j = 0; j < i; j++) { +		struct vop_vringh *vvr = &vdev->vvr[j]; + +		dma_unmap_single(&vpdev->dev, le64_to_cpu(vqconfig[j].address), +				 vvr->vring.len, DMA_BIDIRECTIONAL); +		free_pages((unsigned long)vvr->vring.va, +			   get_order(vvr->vring.len)); +	} +	return ret; +} + +static void vop_dev_remove(struct vop_info *pvi, struct mic_device_ctrl *devp, +			   struct vop_device *vpdev) +{ +	struct mic_bootparam *bootparam = vpdev->hw_ops->get_dp(vpdev); +	s8 db; +	int ret, retry; +	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake); + +	devp->config_change = MIC_VIRTIO_PARAM_DEV_REMOVE; +	db = bootparam->h2c_config_db; +	if (db != -1) +		vpdev->hw_ops->send_intr(vpdev, db); +	else +		goto done; +	for (retry = 15; retry--;) { +		ret = wait_event_timeout(wake, devp->guest_ack, +					 msecs_to_jiffies(1000)); +		if (ret) +			break; +	} +done: +	devp->config_change = 0; +	devp->guest_ack = 0; +} + +static void vop_virtio_del_device(struct vop_vdev *vdev) +{ +	struct vop_info *vi = vdev->vi; +	struct vop_device *vpdev = vdev->vpdev; +	int i; +	struct mic_vqconfig *vqconfig; +	struct mic_bootparam *bootparam = vpdev->hw_ops->get_dp(vpdev); + +	if (!bootparam) +		goto skip_hot_remove; +	vop_dev_remove(vi, vdev->dc, vpdev); +skip_hot_remove: +	vpdev->hw_ops->free_irq(vpdev, vdev->virtio_cookie, vdev); +	flush_work(&vdev->virtio_bh_work); +	vqconfig = mic_vq_config(vdev->dd); +	for (i = 0; i < vdev->dd->num_vq; i++) { +		struct vop_vringh *vvr = &vdev->vvr[i]; + +		dma_unmap_single(&vpdev->dev, +				 vvr->buf_da, VOP_INT_DMA_BUF_SIZE, +				 DMA_BIDIRECTIONAL); +		free_pages((unsigned long)vvr->buf, +			   get_order(VOP_INT_DMA_BUF_SIZE)); +		vringh_kiov_cleanup(&vvr->riov); +		vringh_kiov_cleanup(&vvr->wiov); +		dma_unmap_single(&vpdev->dev, le64_to_cpu(vqconfig[i].address), +				 vvr->vring.len, DMA_BIDIRECTIONAL); +		free_pages((unsigned long)vvr->vring.va, +			   get_order(vvr->vring.len)); +	} +	/* +	 * Order the type update with previous stores. This write barrier +	 * is paired with the corresponding read barrier before the uncached +	 * system memory read of the type, on the card while scanning the +	 * device page. +	 */ +	smp_wmb(); +	vdev->dd->type = -1; +} + +/* + * vop_sync_dma - Wrapper for synchronous DMAs. + * + * @dev - The address of the pointer to the device instance used + * for DMA registration. + * @dst - destination DMA address. + * @src - source DMA address. + * @len - size of the transfer. + * + * Return DMA_SUCCESS on success + */ +static int vop_sync_dma(struct vop_vdev *vdev, dma_addr_t dst, dma_addr_t src, +			size_t len) +{ +	int err = 0; +	struct dma_device *ddev; +	struct dma_async_tx_descriptor *tx; +	struct vop_info *vi = dev_get_drvdata(&vdev->vpdev->dev); +	struct dma_chan *vop_ch = vi->dma_ch; + +	if (!vop_ch) { +		err = -EBUSY; +		goto error; +	} +	ddev = vop_ch->device; +	tx = ddev->device_prep_dma_memcpy(vop_ch, dst, src, len, +		DMA_PREP_FENCE); +	if (!tx) { +		err = -ENOMEM; +		goto error; +	} else { +		dma_cookie_t cookie; + +		cookie = tx->tx_submit(tx); +		if (dma_submit_error(cookie)) { +			err = -ENOMEM; +			goto error; +		} +		dma_async_issue_pending(vop_ch); +		err = dma_sync_wait(vop_ch, cookie); +	} +error: +	if (err) +		dev_err(&vi->vpdev->dev, "%s %d err %d\n", +			__func__, __LINE__, err); +	return err; +} + +#define VOP_USE_DMA true + +/* + * Initiates the copies across the PCIe bus from card memory to a user + * space buffer. When transfers are done using DMA, source/destination + * addresses and transfer length must follow the alignment requirements of + * the MIC DMA engine. + */ +static int vop_virtio_copy_to_user(struct vop_vdev *vdev, void __user *ubuf, +				   size_t len, u64 daddr, size_t dlen, +				   int vr_idx) +{ +	struct vop_device *vpdev = vdev->vpdev; +	void __iomem *dbuf = vpdev->hw_ops->ioremap(vpdev, daddr, len); +	struct vop_vringh *vvr = &vdev->vvr[vr_idx]; +	struct vop_info *vi = dev_get_drvdata(&vpdev->dev); +	size_t dma_alignment = 1 << vi->dma_ch->device->copy_align; +	bool x200 = is_dma_copy_aligned(vi->dma_ch->device, 1, 1, 1); +	size_t dma_offset, partlen; +	int err; + +	if (!VOP_USE_DMA) { +		if (copy_to_user(ubuf, (void __force *)dbuf, len)) { +			err = -EFAULT; +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, err); +			goto err; +		} +		vdev->in_bytes += len; +		err = 0; +		goto err; +	} + +	dma_offset = daddr - round_down(daddr, dma_alignment); +	daddr -= dma_offset; +	len += dma_offset; +	/* +	 * X100 uses DMA addresses as seen by the card so adding +	 * the aperture base is not required for DMA. However x200 +	 * requires DMA addresses to be an offset into the bar so +	 * add the aperture base for x200. +	 */ +	if (x200) +		daddr += vpdev->aper->pa; +	while (len) { +		partlen = min_t(size_t, len, VOP_INT_DMA_BUF_SIZE); +		err = vop_sync_dma(vdev, vvr->buf_da, daddr, +				   ALIGN(partlen, dma_alignment)); +		if (err) { +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, err); +			goto err; +		} +		if (copy_to_user(ubuf, vvr->buf + dma_offset, +				 partlen - dma_offset)) { +			err = -EFAULT; +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, err); +			goto err; +		} +		daddr += partlen; +		ubuf += partlen; +		dbuf += partlen; +		vdev->in_bytes_dma += partlen; +		vdev->in_bytes += partlen; +		len -= partlen; +		dma_offset = 0; +	} +	err = 0; +err: +	vpdev->hw_ops->iounmap(vpdev, dbuf); +	dev_dbg(vop_dev(vdev), +		"%s: ubuf %p dbuf %p len 0x%lx vr_idx 0x%x\n", +		__func__, ubuf, dbuf, len, vr_idx); +	return err; +} + +/* + * Initiates copies across the PCIe bus from a user space buffer to card + * memory. When transfers are done using DMA, source/destination addresses + * and transfer length must follow the alignment requirements of the MIC + * DMA engine. + */ +static int vop_virtio_copy_from_user(struct vop_vdev *vdev, void __user *ubuf, +				     size_t len, u64 daddr, size_t dlen, +				     int vr_idx) +{ +	struct vop_device *vpdev = vdev->vpdev; +	void __iomem *dbuf = vpdev->hw_ops->ioremap(vpdev, daddr, len); +	struct vop_vringh *vvr = &vdev->vvr[vr_idx]; +	struct vop_info *vi = dev_get_drvdata(&vdev->vpdev->dev); +	size_t dma_alignment = 1 << vi->dma_ch->device->copy_align; +	bool x200 = is_dma_copy_aligned(vi->dma_ch->device, 1, 1, 1); +	size_t partlen; +	bool dma = VOP_USE_DMA; +	int err = 0; + +	if (daddr & (dma_alignment - 1)) { +		vdev->tx_dst_unaligned += len; +		dma = false; +	} else if (ALIGN(len, dma_alignment) > dlen) { +		vdev->tx_len_unaligned += len; +		dma = false; +	} + +	if (!dma) +		goto memcpy; + +	/* +	 * X100 uses DMA addresses as seen by the card so adding +	 * the aperture base is not required for DMA. However x200 +	 * requires DMA addresses to be an offset into the bar so +	 * add the aperture base for x200. +	 */ +	if (x200) +		daddr += vpdev->aper->pa; +	while (len) { +		partlen = min_t(size_t, len, VOP_INT_DMA_BUF_SIZE); + +		if (copy_from_user(vvr->buf, ubuf, partlen)) { +			err = -EFAULT; +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, err); +			goto err; +		} +		err = vop_sync_dma(vdev, daddr, vvr->buf_da, +				   ALIGN(partlen, dma_alignment)); +		if (err) { +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, err); +			goto err; +		} +		daddr += partlen; +		ubuf += partlen; +		dbuf += partlen; +		vdev->out_bytes_dma += partlen; +		vdev->out_bytes += partlen; +		len -= partlen; +	} +memcpy: +	/* +	 * We are copying to IO below and should ideally use something +	 * like copy_from_user_toio(..) if it existed. +	 */ +	if (copy_from_user((void __force *)dbuf, ubuf, len)) { +		err = -EFAULT; +		dev_err(vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, err); +		goto err; +	} +	vdev->out_bytes += len; +	err = 0; +err: +	vpdev->hw_ops->iounmap(vpdev, dbuf); +	dev_dbg(vop_dev(vdev), +		"%s: ubuf %p dbuf %p len 0x%lx vr_idx 0x%x\n", +		__func__, ubuf, dbuf, len, vr_idx); +	return err; +} + +#define MIC_VRINGH_READ true + +/* Determine the total number of bytes consumed in a VRINGH KIOV */ +static inline u32 vop_vringh_iov_consumed(struct vringh_kiov *iov) +{ +	int i; +	u32 total = iov->consumed; + +	for (i = 0; i < iov->i; i++) +		total += iov->iov[i].iov_len; +	return total; +} + +/* + * Traverse the VRINGH KIOV and issue the APIs to trigger the copies. + * This API is heavily based on the vringh_iov_xfer(..) implementation + * in vringh.c. The reason we cannot reuse vringh_iov_pull_kern(..) + * and vringh_iov_push_kern(..) directly is because there is no + * way to override the VRINGH xfer(..) routines as of v3.10. + */ +static int vop_vringh_copy(struct vop_vdev *vdev, struct vringh_kiov *iov, +			   void __user *ubuf, size_t len, bool read, int vr_idx, +			   size_t *out_len) +{ +	int ret = 0; +	size_t partlen, tot_len = 0; + +	while (len && iov->i < iov->used) { +		struct kvec *kiov = &iov->iov[iov->i]; + +		partlen = min(kiov->iov_len, len); +		if (read) +			ret = vop_virtio_copy_to_user(vdev, ubuf, partlen, +						      (u64)kiov->iov_base, +						      kiov->iov_len, +						      vr_idx); +		else +			ret = vop_virtio_copy_from_user(vdev, ubuf, partlen, +							(u64)kiov->iov_base, +							kiov->iov_len, +							vr_idx); +		if (ret) { +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, ret); +			break; +		} +		len -= partlen; +		ubuf += partlen; +		tot_len += partlen; +		iov->consumed += partlen; +		kiov->iov_len -= partlen; +		kiov->iov_base += partlen; +		if (!kiov->iov_len) { +			/* Fix up old iov element then increment. */ +			kiov->iov_len = iov->consumed; +			kiov->iov_base -= iov->consumed; + +			iov->consumed = 0; +			iov->i++; +		} +	} +	*out_len = tot_len; +	return ret; +} + +/* + * Use the standard VRINGH infrastructure in the kernel to fetch new + * descriptors, initiate the copies and update the used ring. + */ +static int _vop_virtio_copy(struct vop_vdev *vdev, struct mic_copy_desc *copy) +{ +	int ret = 0; +	u32 iovcnt = copy->iovcnt; +	struct iovec iov; +	struct iovec __user *u_iov = copy->iov; +	void __user *ubuf = NULL; +	struct vop_vringh *vvr = &vdev->vvr[copy->vr_idx]; +	struct vringh_kiov *riov = &vvr->riov; +	struct vringh_kiov *wiov = &vvr->wiov; +	struct vringh *vrh = &vvr->vrh; +	u16 *head = &vvr->head; +	struct mic_vring *vr = &vvr->vring; +	size_t len = 0, out_len; + +	copy->out_len = 0; +	/* Fetch a new IOVEC if all previous elements have been processed */ +	if (riov->i == riov->used && wiov->i == wiov->used) { +		ret = vringh_getdesc_kern(vrh, riov, wiov, +					  head, GFP_KERNEL); +		/* Check if there are available descriptors */ +		if (ret <= 0) +			return ret; +	} +	while (iovcnt) { +		if (!len) { +			/* Copy over a new iovec from user space. */ +			ret = copy_from_user(&iov, u_iov, sizeof(*u_iov)); +			if (ret) { +				ret = -EINVAL; +				dev_err(vop_dev(vdev), "%s %d err %d\n", +					__func__, __LINE__, ret); +				break; +			} +			len = iov.iov_len; +			ubuf = iov.iov_base; +		} +		/* Issue all the read descriptors first */ +		ret = vop_vringh_copy(vdev, riov, ubuf, len, +				      MIC_VRINGH_READ, copy->vr_idx, &out_len); +		if (ret) { +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, ret); +			break; +		} +		len -= out_len; +		ubuf += out_len; +		copy->out_len += out_len; +		/* Issue the write descriptors next */ +		ret = vop_vringh_copy(vdev, wiov, ubuf, len, +				      !MIC_VRINGH_READ, copy->vr_idx, &out_len); +		if (ret) { +			dev_err(vop_dev(vdev), "%s %d err %d\n", +				__func__, __LINE__, ret); +			break; +		} +		len -= out_len; +		ubuf += out_len; +		copy->out_len += out_len; +		if (!len) { +			/* One user space iovec is now completed */ +			iovcnt--; +			u_iov++; +		} +		/* Exit loop if all elements in KIOVs have been processed. */ +		if (riov->i == riov->used && wiov->i == wiov->used) +			break; +	} +	/* +	 * Update the used ring if a descriptor was available and some data was +	 * copied in/out and the user asked for a used ring update. +	 */ +	if (*head != USHRT_MAX && copy->out_len && copy->update_used) { +		u32 total = 0; + +		/* Determine the total data consumed */ +		total += vop_vringh_iov_consumed(riov); +		total += vop_vringh_iov_consumed(wiov); +		vringh_complete_kern(vrh, *head, total); +		*head = USHRT_MAX; +		if (vringh_need_notify_kern(vrh) > 0) +			vringh_notify(vrh); +		vringh_kiov_cleanup(riov); +		vringh_kiov_cleanup(wiov); +		/* Update avail idx for user space */ +		vr->info->avail_idx = vrh->last_avail_idx; +	} +	return ret; +} + +static inline int vop_verify_copy_args(struct vop_vdev *vdev, +				       struct mic_copy_desc *copy) +{ +	if (!vdev || copy->vr_idx >= vdev->dd->num_vq) +		return -EINVAL; +	return 0; +} + +/* Copy a specified number of virtio descriptors in a chain */ +static int vop_virtio_copy_desc(struct vop_vdev *vdev, +				struct mic_copy_desc *copy) +{ +	int err; +	struct vop_vringh *vvr; + +	err = vop_verify_copy_args(vdev, copy); +	if (err) +		return err; + +	vvr = &vdev->vvr[copy->vr_idx]; +	mutex_lock(&vvr->vr_mutex); +	if (!vop_vdevup(vdev)) { +		err = -ENODEV; +		dev_err(vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, err); +		goto err; +	} +	err = _vop_virtio_copy(vdev, copy); +	if (err) { +		dev_err(vop_dev(vdev), "%s %d err %d\n", +			__func__, __LINE__, err); +	} +err: +	mutex_unlock(&vvr->vr_mutex); +	return err; +} + +static int vop_open(struct inode *inode, struct file *f) +{ +	struct vop_vdev *vdev; +	struct vop_info *vi = container_of(f->private_data, +		struct vop_info, miscdev); + +	vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); +	if (!vdev) +		return -ENOMEM; +	vdev->vi = vi; +	mutex_init(&vdev->vdev_mutex); +	f->private_data = vdev; +	init_completion(&vdev->destroy); +	complete(&vdev->destroy); +	return 0; +} + +static int vop_release(struct inode *inode, struct file *f) +{ +	struct vop_vdev *vdev = f->private_data, *vdev_tmp; +	struct vop_info *vi = vdev->vi; +	struct list_head *pos, *tmp; +	bool found = false; + +	mutex_lock(&vdev->vdev_mutex); +	if (vdev->deleted) +		goto unlock; +	mutex_lock(&vi->vop_mutex); +	list_for_each_safe(pos, tmp, &vi->vdev_list) { +		vdev_tmp = list_entry(pos, struct vop_vdev, list); +		if (vdev == vdev_tmp) { +			vop_virtio_del_device(vdev); +			list_del(pos); +			found = true; +			break; +		} +	} +	mutex_unlock(&vi->vop_mutex); +unlock: +	mutex_unlock(&vdev->vdev_mutex); +	if (!found) +		wait_for_completion(&vdev->destroy); +	f->private_data = NULL; +	kfree(vdev); +	return 0; +} + +static long vop_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ +	struct vop_vdev *vdev = f->private_data; +	struct vop_info *vi = vdev->vi; +	void __user *argp = (void __user *)arg; +	int ret; + +	switch (cmd) { +	case MIC_VIRTIO_ADD_DEVICE: +	{ +		struct mic_device_desc dd, *dd_config; + +		if (copy_from_user(&dd, argp, sizeof(dd))) +			return -EFAULT; + +		if (mic_aligned_desc_size(&dd) > MIC_MAX_DESC_BLK_SIZE || +		    dd.num_vq > MIC_MAX_VRINGS) +			return -EINVAL; + +		dd_config = kzalloc(mic_desc_size(&dd), GFP_KERNEL); +		if (!dd_config) +			return -ENOMEM; +		if (copy_from_user(dd_config, argp, mic_desc_size(&dd))) { +			ret = -EFAULT; +			goto free_ret; +		} +		mutex_lock(&vdev->vdev_mutex); +		mutex_lock(&vi->vop_mutex); +		ret = vop_virtio_add_device(vdev, dd_config); +		if (ret) +			goto unlock_ret; +		list_add_tail(&vdev->list, &vi->vdev_list); +unlock_ret: +		mutex_unlock(&vi->vop_mutex); +		mutex_unlock(&vdev->vdev_mutex); +free_ret: +		kfree(dd_config); +		return ret; +	} +	case MIC_VIRTIO_COPY_DESC: +	{ +		struct mic_copy_desc copy; + +		mutex_lock(&vdev->vdev_mutex); +		ret = vop_vdev_inited(vdev); +		if (ret) +			goto _unlock_ret; + +		if (copy_from_user(©, argp, sizeof(copy))) { +			ret = -EFAULT; +			goto _unlock_ret; +		} + +		ret = vop_virtio_copy_desc(vdev, ©); +		if (ret < 0) +			goto _unlock_ret; +		if (copy_to_user( +			&((struct mic_copy_desc __user *)argp)->out_len, +			©.out_len, sizeof(copy.out_len))) +			ret = -EFAULT; +_unlock_ret: +		mutex_unlock(&vdev->vdev_mutex); +		return ret; +	} +	case MIC_VIRTIO_CONFIG_CHANGE: +	{ +		void *buf; + +		mutex_lock(&vdev->vdev_mutex); +		ret = vop_vdev_inited(vdev); +		if (ret) +			goto __unlock_ret; +		buf = kzalloc(vdev->dd->config_len, GFP_KERNEL); +		if (!buf) { +			ret = -ENOMEM; +			goto __unlock_ret; +		} +		if (copy_from_user(buf, argp, vdev->dd->config_len)) { +			ret = -EFAULT; +			goto done; +		} +		ret = vop_virtio_config_change(vdev, buf); +done: +		kfree(buf); +__unlock_ret: +		mutex_unlock(&vdev->vdev_mutex); +		return ret; +	} +	default: +		return -ENOIOCTLCMD; +	}; +	return 0; +} + +/* + * We return POLLIN | POLLOUT from poll when new buffers are enqueued, and + * not when previously enqueued buffers may be available. This means that + * in the card->host (TX) path, when userspace is unblocked by poll it + * must drain all available descriptors or it can stall. + */ +static unsigned int vop_poll(struct file *f, poll_table *wait) +{ +	struct vop_vdev *vdev = f->private_data; +	int mask = 0; + +	mutex_lock(&vdev->vdev_mutex); +	if (vop_vdev_inited(vdev)) { +		mask = POLLERR; +		goto done; +	} +	poll_wait(f, &vdev->waitq, wait); +	if (vop_vdev_inited(vdev)) { +		mask = POLLERR; +	} else if (vdev->poll_wake) { +		vdev->poll_wake = 0; +		mask = POLLIN | POLLOUT; +	} +done: +	mutex_unlock(&vdev->vdev_mutex); +	return mask; +} + +static inline int +vop_query_offset(struct vop_vdev *vdev, unsigned long offset, +		 unsigned long *size, unsigned long *pa) +{ +	struct vop_device *vpdev = vdev->vpdev; +	unsigned long start = MIC_DP_SIZE; +	int i; + +	/* +	 * MMAP interface is as follows: +	 * offset				region +	 * 0x0					virtio device_page +	 * 0x1000				first vring +	 * 0x1000 + size of 1st vring		second vring +	 * .... +	 */ +	if (!offset) { +		*pa = virt_to_phys(vpdev->hw_ops->get_dp(vpdev)); +		*size = MIC_DP_SIZE; +		return 0; +	} + +	for (i = 0; i < vdev->dd->num_vq; i++) { +		struct vop_vringh *vvr = &vdev->vvr[i]; + +		if (offset == start) { +			*pa = virt_to_phys(vvr->vring.va); +			*size = vvr->vring.len; +			return 0; +		} +		start += vvr->vring.len; +	} +	return -1; +} + +/* + * Maps the device page and virtio rings to user space for readonly access. + */ +static int vop_mmap(struct file *f, struct vm_area_struct *vma) +{ +	struct vop_vdev *vdev = f->private_data; +	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; +	unsigned long pa, size = vma->vm_end - vma->vm_start, size_rem = size; +	int i, err; + +	err = vop_vdev_inited(vdev); +	if (err) +		goto ret; +	if (vma->vm_flags & VM_WRITE) { +		err = -EACCES; +		goto ret; +	} +	while (size_rem) { +		i = vop_query_offset(vdev, offset, &size, &pa); +		if (i < 0) { +			err = -EINVAL; +			goto ret; +		} +		err = remap_pfn_range(vma, vma->vm_start + offset, +				      pa >> PAGE_SHIFT, size, +				      vma->vm_page_prot); +		if (err) +			goto ret; +		size_rem -= size; +		offset += size; +	} +ret: +	return err; +} + +static const struct file_operations vop_fops = { +	.open = vop_open, +	.release = vop_release, +	.unlocked_ioctl = vop_ioctl, +	.poll = vop_poll, +	.mmap = vop_mmap, +	.owner = THIS_MODULE, +}; + +int vop_host_init(struct vop_info *vi) +{ +	int rc; +	struct miscdevice *mdev; +	struct vop_device *vpdev = vi->vpdev; + +	INIT_LIST_HEAD(&vi->vdev_list); +	vi->dma_ch = vpdev->dma_ch; +	mdev = &vi->miscdev; +	mdev->minor = MISC_DYNAMIC_MINOR; +	snprintf(vi->name, sizeof(vi->name), "vop_virtio%d", vpdev->index); +	mdev->name = vi->name; +	mdev->fops = &vop_fops; +	mdev->parent = &vpdev->dev; + +	rc = misc_register(mdev); +	if (rc) +		dev_err(&vpdev->dev, "%s failed rc %d\n", __func__, rc); +	return rc; +} + +void vop_host_uninit(struct vop_info *vi) +{ +	struct list_head *pos, *tmp; +	struct vop_vdev *vdev; + +	mutex_lock(&vi->vop_mutex); +	vop_virtio_reset_devices(vi); +	list_for_each_safe(pos, tmp, &vi->vdev_list) { +		vdev = list_entry(pos, struct vop_vdev, list); +		list_del(pos); +		reinit_completion(&vdev->destroy); +		mutex_unlock(&vi->vop_mutex); +		mutex_lock(&vdev->vdev_mutex); +		vop_virtio_del_device(vdev); +		vdev->deleted = true; +		mutex_unlock(&vdev->vdev_mutex); +		complete(&vdev->destroy); +		mutex_lock(&vi->vop_mutex); +	} +	mutex_unlock(&vi->vop_mutex); +	misc_deregister(&vi->miscdev); +} diff --git a/drivers/misc/panel.c b/drivers/misc/panel.c new file mode 100644 index 000000000000..6030ac5b8c63 --- /dev/null +++ b/drivers/misc/panel.c @@ -0,0 +1,2438 @@ +/* + * Front panel driver for Linux + * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> + * + * 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 code drives an LCD module (/dev/lcd), and a keypad (/dev/keypad) + * connected to a parallel printer port. + * + * The LCD module may either be an HD44780-like 8-bit parallel LCD, or a 1-bit + * serial module compatible with Samsung's KS0074. The pins may be connected in + * any combination, everything is programmable. + * + * The keypad consists in a matrix of push buttons connecting input pins to + * data output pins or to the ground. The combinations have to be hard-coded + * in the driver, though several profiles exist and adding new ones is easy. + * + * Several profiles are provided for commonly found LCD+keypad modules on the + * market, such as those found in Nexcom's appliances. + * + * FIXME: + *      - the initialization/deinitialization process is very dirty and should + *        be rewritten. It may even be buggy. + * + * TODO: + *	- document 24 keys keyboard (3 rows of 8 cols, 32 diodes + 2 inputs) + *      - make the LCD a part of a virtual screen of Vx*Vy + *	- make the inputs list smp-safe + *      - change the keyboard to a double mapping : signals -> key_id -> values + *        so that applications can change values without knowing signals + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/ctype.h> +#include <linux/parport.h> +#include <linux/list.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <generated/utsrelease.h> + +#include <linux/io.h> +#include <linux/uaccess.h> + +#define LCD_MINOR		156 +#define KEYPAD_MINOR		185 + +#define PANEL_VERSION		"0.9.5" + +#define LCD_MAXBYTES		256	/* max burst write */ + +#define KEYPAD_BUFFER		64 + +/* poll the keyboard this every second */ +#define INPUT_POLL_TIME		(HZ / 50) +/* a key starts to repeat after this times INPUT_POLL_TIME */ +#define KEYPAD_REP_START	(10) +/* a key repeats this times INPUT_POLL_TIME */ +#define KEYPAD_REP_DELAY	(2) + +/* keep the light on this times INPUT_POLL_TIME for each flash */ +#define FLASH_LIGHT_TEMPO	(200) + +/* converts an r_str() input to an active high, bits string : 000BAOSE */ +#define PNL_PINPUT(a)		((((unsigned char)(a)) ^ 0x7F) >> 3) + +#define PNL_PBUSY		0x80	/* inverted input, active low */ +#define PNL_PACK		0x40	/* direct input, active low */ +#define PNL_POUTPA		0x20	/* direct input, active high */ +#define PNL_PSELECD		0x10	/* direct input, active high */ +#define PNL_PERRORP		0x08	/* direct input, active low */ + +#define PNL_PBIDIR		0x20	/* bi-directional ports */ +/* high to read data in or-ed with data out */ +#define PNL_PINTEN		0x10 +#define PNL_PSELECP		0x08	/* inverted output, active low */ +#define PNL_PINITP		0x04	/* direct output, active low */ +#define PNL_PAUTOLF		0x02	/* inverted output, active low */ +#define PNL_PSTROBE		0x01	/* inverted output */ + +#define PNL_PD0			0x01 +#define PNL_PD1			0x02 +#define PNL_PD2			0x04 +#define PNL_PD3			0x08 +#define PNL_PD4			0x10 +#define PNL_PD5			0x20 +#define PNL_PD6			0x40 +#define PNL_PD7			0x80 + +#define PIN_NONE		0 +#define PIN_STROBE		1 +#define PIN_D0			2 +#define PIN_D1			3 +#define PIN_D2			4 +#define PIN_D3			5 +#define PIN_D4			6 +#define PIN_D5			7 +#define PIN_D6			8 +#define PIN_D7			9 +#define PIN_AUTOLF		14 +#define PIN_INITP		16 +#define PIN_SELECP		17 +#define PIN_NOT_SET		127 + +#define LCD_FLAG_S		0x0001 +#define LCD_FLAG_ID		0x0002 +#define LCD_FLAG_B		0x0004	/* blink on */ +#define LCD_FLAG_C		0x0008	/* cursor on */ +#define LCD_FLAG_D		0x0010	/* display on */ +#define LCD_FLAG_F		0x0020	/* large font mode */ +#define LCD_FLAG_N		0x0040	/* 2-rows mode */ +#define LCD_FLAG_L		0x0080	/* backlight enabled */ + +/* LCD commands */ +#define LCD_CMD_DISPLAY_CLEAR	0x01	/* Clear entire display */ + +#define LCD_CMD_ENTRY_MODE	0x04	/* Set entry mode */ +#define LCD_CMD_CURSOR_INC	0x02	/* Increment cursor */ + +#define LCD_CMD_DISPLAY_CTRL	0x08	/* Display control */ +#define LCD_CMD_DISPLAY_ON	0x04	/* Set display on */ +#define LCD_CMD_CURSOR_ON	0x02	/* Set cursor on */ +#define LCD_CMD_BLINK_ON	0x01	/* Set blink on */ + +#define LCD_CMD_SHIFT		0x10	/* Shift cursor/display */ +#define LCD_CMD_DISPLAY_SHIFT	0x08	/* Shift display instead of cursor */ +#define LCD_CMD_SHIFT_RIGHT	0x04	/* Shift display/cursor to the right */ + +#define LCD_CMD_FUNCTION_SET	0x20	/* Set function */ +#define LCD_CMD_DATA_LEN_8BITS	0x10	/* Set data length to 8 bits */ +#define LCD_CMD_TWO_LINES	0x08	/* Set to two display lines */ +#define LCD_CMD_FONT_5X10_DOTS	0x04	/* Set char font to 5x10 dots */ + +#define LCD_CMD_SET_CGRAM_ADDR	0x40	/* Set char generator RAM address */ + +#define LCD_CMD_SET_DDRAM_ADDR	0x80	/* Set display data RAM address */ + +#define LCD_ESCAPE_LEN		24	/* max chars for LCD escape command */ +#define LCD_ESCAPE_CHAR	27	/* use char 27 for escape command */ + +#define NOT_SET			-1 + +/* macros to simplify use of the parallel port */ +#define r_ctr(x)        (parport_read_control((x)->port)) +#define r_dtr(x)        (parport_read_data((x)->port)) +#define r_str(x)        (parport_read_status((x)->port)) +#define w_ctr(x, y)     (parport_write_control((x)->port, (y))) +#define w_dtr(x, y)     (parport_write_data((x)->port, (y))) + +/* this defines which bits are to be used and which ones to be ignored */ +/* logical or of the output bits involved in the scan matrix */ +static __u8 scan_mask_o; +/* logical or of the input bits involved in the scan matrix */ +static __u8 scan_mask_i; + +enum input_type { +	INPUT_TYPE_STD, +	INPUT_TYPE_KBD, +}; + +enum input_state { +	INPUT_ST_LOW, +	INPUT_ST_RISING, +	INPUT_ST_HIGH, +	INPUT_ST_FALLING, +}; + +struct logical_input { +	struct list_head list; +	__u64 mask; +	__u64 value; +	enum input_type type; +	enum input_state state; +	__u8 rise_time, fall_time; +	__u8 rise_timer, fall_timer, high_timer; + +	union { +		struct {	/* valid when type == INPUT_TYPE_STD */ +			void (*press_fct)(int); +			void (*release_fct)(int); +			int press_data; +			int release_data; +		} std; +		struct {	/* valid when type == INPUT_TYPE_KBD */ +			/* strings can be non null-terminated */ +			char press_str[sizeof(void *) + sizeof(int)]; +			char repeat_str[sizeof(void *) + sizeof(int)]; +			char release_str[sizeof(void *) + sizeof(int)]; +		} kbd; +	} u; +}; + +static LIST_HEAD(logical_inputs);	/* list of all defined logical inputs */ + +/* physical contacts history + * Physical contacts are a 45 bits string of 9 groups of 5 bits each. + * The 8 lower groups correspond to output bits 0 to 7, and the 9th group + * corresponds to the ground. + * Within each group, bits are stored in the same order as read on the port : + * BAPSE (busy=4, ack=3, paper empty=2, select=1, error=0). + * So, each __u64 is represented like this : + * 0000000000000000000BAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSE + * <-----unused------><gnd><d07><d06><d05><d04><d03><d02><d01><d00> + */ + +/* what has just been read from the I/O ports */ +static __u64 phys_read; +/* previous phys_read */ +static __u64 phys_read_prev; +/* stabilized phys_read (phys_read|phys_read_prev) */ +static __u64 phys_curr; +/* previous phys_curr */ +static __u64 phys_prev; +/* 0 means that at least one logical signal needs be computed */ +static char inputs_stable; + +/* these variables are specific to the keypad */ +static struct { +	bool enabled; +} keypad; + +static char keypad_buffer[KEYPAD_BUFFER]; +static int keypad_buflen; +static int keypad_start; +static char keypressed; +static wait_queue_head_t keypad_read_wait; + +/* lcd-specific variables */ +static struct { +	bool enabled; +	bool initialized; +	bool must_clear; + +	int height; +	int width; +	int bwidth; +	int hwidth; +	int charset; +	int proto; +	int light_tempo; + +	/* TODO: use union here? */ +	struct { +		int e; +		int rs; +		int rw; +		int cl; +		int da; +		int bl; +	} pins; + +	/* contains the LCD config state */ +	unsigned long int flags; + +	/* Contains the LCD X and Y offset */ +	struct { +		unsigned long int x; +		unsigned long int y; +	} addr; + +	/* Current escape sequence and it's length or -1 if outside */ +	struct { +		char buf[LCD_ESCAPE_LEN + 1]; +		int len; +	} esc_seq; +} lcd; + +/* Needed only for init */ +static int selected_lcd_type = NOT_SET; + +/* + * Bit masks to convert LCD signals to parallel port outputs. + * _d_ are values for data port, _c_ are for control port. + * [0] = signal OFF, [1] = signal ON, [2] = mask + */ +#define BIT_CLR		0 +#define BIT_SET		1 +#define BIT_MSK		2 +#define BIT_STATES	3 +/* + * one entry for each bit on the LCD + */ +#define LCD_BIT_E	0 +#define LCD_BIT_RS	1 +#define LCD_BIT_RW	2 +#define LCD_BIT_BL	3 +#define LCD_BIT_CL	4 +#define LCD_BIT_DA	5 +#define LCD_BITS	6 + +/* + * each bit can be either connected to a DATA or CTRL port + */ +#define LCD_PORT_C	0 +#define LCD_PORT_D	1 +#define LCD_PORTS	2 + +static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES]; + +/* + * LCD protocols + */ +#define LCD_PROTO_PARALLEL      0 +#define LCD_PROTO_SERIAL        1 +#define LCD_PROTO_TI_DA8XX_LCD	2 + +/* + * LCD character sets + */ +#define LCD_CHARSET_NORMAL      0 +#define LCD_CHARSET_KS0074      1 + +/* + * LCD types + */ +#define LCD_TYPE_NONE		0 +#define LCD_TYPE_CUSTOM		1 +#define LCD_TYPE_OLD		2 +#define LCD_TYPE_KS0074		3 +#define LCD_TYPE_HANTRONIX	4 +#define LCD_TYPE_NEXCOM		5 + +/* + * keypad types + */ +#define KEYPAD_TYPE_NONE	0 +#define KEYPAD_TYPE_OLD		1 +#define KEYPAD_TYPE_NEW		2 +#define KEYPAD_TYPE_NEXCOM	3 + +/* + * panel profiles + */ +#define PANEL_PROFILE_CUSTOM	0 +#define PANEL_PROFILE_OLD	1 +#define PANEL_PROFILE_NEW	2 +#define PANEL_PROFILE_HANTRONIX	3 +#define PANEL_PROFILE_NEXCOM	4 +#define PANEL_PROFILE_LARGE	5 + +/* + * Construct custom config from the kernel's configuration + */ +#define DEFAULT_PARPORT         0 +#define DEFAULT_PROFILE         PANEL_PROFILE_LARGE +#define DEFAULT_KEYPAD_TYPE     KEYPAD_TYPE_OLD +#define DEFAULT_LCD_TYPE        LCD_TYPE_OLD +#define DEFAULT_LCD_HEIGHT      2 +#define DEFAULT_LCD_WIDTH       40 +#define DEFAULT_LCD_BWIDTH      40 +#define DEFAULT_LCD_HWIDTH      64 +#define DEFAULT_LCD_CHARSET     LCD_CHARSET_NORMAL +#define DEFAULT_LCD_PROTO       LCD_PROTO_PARALLEL + +#define DEFAULT_LCD_PIN_E       PIN_AUTOLF +#define DEFAULT_LCD_PIN_RS      PIN_SELECP +#define DEFAULT_LCD_PIN_RW      PIN_INITP +#define DEFAULT_LCD_PIN_SCL     PIN_STROBE +#define DEFAULT_LCD_PIN_SDA     PIN_D0 +#define DEFAULT_LCD_PIN_BL      PIN_NOT_SET + +#ifdef CONFIG_PANEL_PARPORT +#undef DEFAULT_PARPORT +#define DEFAULT_PARPORT CONFIG_PANEL_PARPORT +#endif + +#ifdef CONFIG_PANEL_PROFILE +#undef DEFAULT_PROFILE +#define DEFAULT_PROFILE CONFIG_PANEL_PROFILE +#endif + +#if DEFAULT_PROFILE == 0	/* custom */ +#ifdef CONFIG_PANEL_KEYPAD +#undef DEFAULT_KEYPAD_TYPE +#define DEFAULT_KEYPAD_TYPE CONFIG_PANEL_KEYPAD +#endif + +#ifdef CONFIG_PANEL_LCD +#undef DEFAULT_LCD_TYPE +#define DEFAULT_LCD_TYPE CONFIG_PANEL_LCD +#endif + +#ifdef CONFIG_PANEL_LCD_HEIGHT +#undef DEFAULT_LCD_HEIGHT +#define DEFAULT_LCD_HEIGHT CONFIG_PANEL_LCD_HEIGHT +#endif + +#ifdef CONFIG_PANEL_LCD_WIDTH +#undef DEFAULT_LCD_WIDTH +#define DEFAULT_LCD_WIDTH CONFIG_PANEL_LCD_WIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_BWIDTH +#undef DEFAULT_LCD_BWIDTH +#define DEFAULT_LCD_BWIDTH CONFIG_PANEL_LCD_BWIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_HWIDTH +#undef DEFAULT_LCD_HWIDTH +#define DEFAULT_LCD_HWIDTH CONFIG_PANEL_LCD_HWIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_CHARSET +#undef DEFAULT_LCD_CHARSET +#define DEFAULT_LCD_CHARSET CONFIG_PANEL_LCD_CHARSET +#endif + +#ifdef CONFIG_PANEL_LCD_PROTO +#undef DEFAULT_LCD_PROTO +#define DEFAULT_LCD_PROTO CONFIG_PANEL_LCD_PROTO +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_E +#undef DEFAULT_LCD_PIN_E +#define DEFAULT_LCD_PIN_E CONFIG_PANEL_LCD_PIN_E +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_RS +#undef DEFAULT_LCD_PIN_RS +#define DEFAULT_LCD_PIN_RS CONFIG_PANEL_LCD_PIN_RS +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_RW +#undef DEFAULT_LCD_PIN_RW +#define DEFAULT_LCD_PIN_RW CONFIG_PANEL_LCD_PIN_RW +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_SCL +#undef DEFAULT_LCD_PIN_SCL +#define DEFAULT_LCD_PIN_SCL CONFIG_PANEL_LCD_PIN_SCL +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_SDA +#undef DEFAULT_LCD_PIN_SDA +#define DEFAULT_LCD_PIN_SDA CONFIG_PANEL_LCD_PIN_SDA +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_BL +#undef DEFAULT_LCD_PIN_BL +#define DEFAULT_LCD_PIN_BL CONFIG_PANEL_LCD_PIN_BL +#endif + +#endif /* DEFAULT_PROFILE == 0 */ + +/* global variables */ + +/* Device single-open policy control */ +static atomic_t lcd_available = ATOMIC_INIT(1); +static atomic_t keypad_available = ATOMIC_INIT(1); + +static struct pardevice *pprt; + +static int keypad_initialized; + +static void (*lcd_write_cmd)(int); +static void (*lcd_write_data)(int); +static void (*lcd_clear_fast)(void); + +static DEFINE_SPINLOCK(pprt_lock); +static struct timer_list scan_timer; + +MODULE_DESCRIPTION("Generic parallel port LCD/Keypad driver"); + +static int parport = DEFAULT_PARPORT; +module_param(parport, int, 0000); +MODULE_PARM_DESC(parport, "Parallel port index (0=lpt1, 1=lpt2, ...)"); + +static int profile = DEFAULT_PROFILE; +module_param(profile, int, 0000); +MODULE_PARM_DESC(profile, +		 "1=16x2 old kp; 2=serial 16x2, new kp; 3=16x2 hantronix; " +		 "4=16x2 nexcom; default=40x2, old kp"); + +static int keypad_type = NOT_SET; +module_param(keypad_type, int, 0000); +MODULE_PARM_DESC(keypad_type, +		 "Keypad type: 0=none, 1=old 6 keys, 2=new 6+1 keys, 3=nexcom 4 keys"); + +static int lcd_type = NOT_SET; +module_param(lcd_type, int, 0000); +MODULE_PARM_DESC(lcd_type, +		 "LCD type: 0=none, 1=compiled-in, 2=old, 3=serial ks0074, 4=hantronix, 5=nexcom"); + +static int lcd_height = NOT_SET; +module_param(lcd_height, int, 0000); +MODULE_PARM_DESC(lcd_height, "Number of lines on the LCD"); + +static int lcd_width = NOT_SET; +module_param(lcd_width, int, 0000); +MODULE_PARM_DESC(lcd_width, "Number of columns on the LCD"); + +static int lcd_bwidth = NOT_SET;	/* internal buffer width (usually 40) */ +module_param(lcd_bwidth, int, 0000); +MODULE_PARM_DESC(lcd_bwidth, "Internal LCD line width (40)"); + +static int lcd_hwidth = NOT_SET;	/* hardware buffer width (usually 64) */ +module_param(lcd_hwidth, int, 0000); +MODULE_PARM_DESC(lcd_hwidth, "LCD line hardware address (64)"); + +static int lcd_charset = NOT_SET; +module_param(lcd_charset, int, 0000); +MODULE_PARM_DESC(lcd_charset, "LCD character set: 0=standard, 1=KS0074"); + +static int lcd_proto = NOT_SET; +module_param(lcd_proto, int, 0000); +MODULE_PARM_DESC(lcd_proto, +		 "LCD communication: 0=parallel (//), 1=serial, 2=TI LCD Interface"); + +/* + * These are the parallel port pins the LCD control signals are connected to. + * Set this to 0 if the signal is not used. Set it to its opposite value + * (negative) if the signal is negated. -MAXINT is used to indicate that the + * pin has not been explicitly specified. + * + * WARNING! no check will be performed about collisions with keypad ! + */ + +static int lcd_e_pin  = PIN_NOT_SET; +module_param(lcd_e_pin, int, 0000); +MODULE_PARM_DESC(lcd_e_pin, +		 "# of the // port pin connected to LCD 'E' signal, with polarity (-17..17)"); + +static int lcd_rs_pin = PIN_NOT_SET; +module_param(lcd_rs_pin, int, 0000); +MODULE_PARM_DESC(lcd_rs_pin, +		 "# of the // port pin connected to LCD 'RS' signal, with polarity (-17..17)"); + +static int lcd_rw_pin = PIN_NOT_SET; +module_param(lcd_rw_pin, int, 0000); +MODULE_PARM_DESC(lcd_rw_pin, +		 "# of the // port pin connected to LCD 'RW' signal, with polarity (-17..17)"); + +static int lcd_cl_pin = PIN_NOT_SET; +module_param(lcd_cl_pin, int, 0000); +MODULE_PARM_DESC(lcd_cl_pin, +		 "# of the // port pin connected to serial LCD 'SCL' signal, with polarity (-17..17)"); + +static int lcd_da_pin = PIN_NOT_SET; +module_param(lcd_da_pin, int, 0000); +MODULE_PARM_DESC(lcd_da_pin, +		 "# of the // port pin connected to serial LCD 'SDA' signal, with polarity (-17..17)"); + +static int lcd_bl_pin = PIN_NOT_SET; +module_param(lcd_bl_pin, int, 0000); +MODULE_PARM_DESC(lcd_bl_pin, +		 "# of the // port pin connected to LCD backlight, with polarity (-17..17)"); + +/* Deprecated module parameters - consider not using them anymore */ + +static int lcd_enabled = NOT_SET; +module_param(lcd_enabled, int, 0000); +MODULE_PARM_DESC(lcd_enabled, "Deprecated option, use lcd_type instead"); + +static int keypad_enabled = NOT_SET; +module_param(keypad_enabled, int, 0000); +MODULE_PARM_DESC(keypad_enabled, "Deprecated option, use keypad_type instead"); + +static const unsigned char *lcd_char_conv; + +/* for some LCD drivers (ks0074) we need a charset conversion table. */ +static const unsigned char lcd_char_conv_ks0074[256] = { +	/*          0|8   1|9   2|A   3|B   4|C   5|D   6|E   7|F */ +	/* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, +	/* 0x08 */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +	/* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, +	/* 0x18 */ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +	/* 0x20 */ 0x20, 0x21, 0x22, 0x23, 0xa2, 0x25, 0x26, 0x27, +	/* 0x28 */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, +	/* 0x30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +	/* 0x38 */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, +	/* 0x40 */ 0xa0, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, +	/* 0x48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, +	/* 0x50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, +	/* 0x58 */ 0x58, 0x59, 0x5a, 0xfa, 0xfb, 0xfc, 0x1d, 0xc4, +	/* 0x60 */ 0x96, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, +	/* 0x68 */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, +	/* 0x70 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, +	/* 0x78 */ 0x78, 0x79, 0x7a, 0xfd, 0xfe, 0xff, 0xce, 0x20, +	/* 0x80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, +	/* 0x88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, +	/* 0x90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, +	/* 0x98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, +	/* 0xA0 */ 0x20, 0x40, 0xb1, 0xa1, 0x24, 0xa3, 0xfe, 0x5f, +	/* 0xA8 */ 0x22, 0xc8, 0x61, 0x14, 0x97, 0x2d, 0xad, 0x96, +	/* 0xB0 */ 0x80, 0x8c, 0x82, 0x83, 0x27, 0x8f, 0x86, 0xdd, +	/* 0xB8 */ 0x2c, 0x81, 0x6f, 0x15, 0x8b, 0x8a, 0x84, 0x60, +	/* 0xC0 */ 0xe2, 0xe2, 0xe2, 0x5b, 0x5b, 0xae, 0xbc, 0xa9, +	/* 0xC8 */ 0xc5, 0xbf, 0xc6, 0xf1, 0xe3, 0xe3, 0xe3, 0xe3, +	/* 0xD0 */ 0x44, 0x5d, 0xa8, 0xe4, 0xec, 0xec, 0x5c, 0x78, +	/* 0xD8 */ 0xab, 0xa6, 0xe5, 0x5e, 0x5e, 0xe6, 0xaa, 0xbe, +	/* 0xE0 */ 0x7f, 0xe7, 0xaf, 0x7b, 0x7b, 0xaf, 0xbd, 0xc8, +	/* 0xE8 */ 0xa4, 0xa5, 0xc7, 0xf6, 0xa7, 0xe8, 0x69, 0x69, +	/* 0xF0 */ 0xed, 0x7d, 0xa8, 0xe4, 0xec, 0x5c, 0x5c, 0x25, +	/* 0xF8 */ 0xac, 0xa6, 0xea, 0xef, 0x7e, 0xeb, 0xb2, 0x79, +}; + +static const char old_keypad_profile[][4][9] = { +	{"S0", "Left\n", "Left\n", ""}, +	{"S1", "Down\n", "Down\n", ""}, +	{"S2", "Up\n", "Up\n", ""}, +	{"S3", "Right\n", "Right\n", ""}, +	{"S4", "Esc\n", "Esc\n", ""}, +	{"S5", "Ret\n", "Ret\n", ""}, +	{"", "", "", ""} +}; + +/* signals, press, repeat, release */ +static const char new_keypad_profile[][4][9] = { +	{"S0", "Left\n", "Left\n", ""}, +	{"S1", "Down\n", "Down\n", ""}, +	{"S2", "Up\n", "Up\n", ""}, +	{"S3", "Right\n", "Right\n", ""}, +	{"S4s5", "", "Esc\n", "Esc\n"}, +	{"s4S5", "", "Ret\n", "Ret\n"}, +	{"S4S5", "Help\n", "", ""}, +	/* add new signals above this line */ +	{"", "", "", ""} +}; + +/* signals, press, repeat, release */ +static const char nexcom_keypad_profile[][4][9] = { +	{"a-p-e-", "Down\n", "Down\n", ""}, +	{"a-p-E-", "Ret\n", "Ret\n", ""}, +	{"a-P-E-", "Esc\n", "Esc\n", ""}, +	{"a-P-e-", "Up\n", "Up\n", ""}, +	/* add new signals above this line */ +	{"", "", "", ""} +}; + +static const char (*keypad_profile)[4][9] = old_keypad_profile; + +static DECLARE_BITMAP(bits, LCD_BITS); + +static void lcd_get_bits(unsigned int port, int *val) +{ +	unsigned int bit, state; + +	for (bit = 0; bit < LCD_BITS; bit++) { +		state = test_bit(bit, bits) ? BIT_SET : BIT_CLR; +		*val &= lcd_bits[port][bit][BIT_MSK]; +		*val |= lcd_bits[port][bit][state]; +	} +} + +static void init_scan_timer(void); + +/* sets data port bits according to current signals values */ +static int set_data_bits(void) +{ +	int val; + +	val = r_dtr(pprt); +	lcd_get_bits(LCD_PORT_D, &val); +	w_dtr(pprt, val); +	return val; +} + +/* sets ctrl port bits according to current signals values */ +static int set_ctrl_bits(void) +{ +	int val; + +	val = r_ctr(pprt); +	lcd_get_bits(LCD_PORT_C, &val); +	w_ctr(pprt, val); +	return val; +} + +/* sets ctrl & data port bits according to current signals values */ +static void panel_set_bits(void) +{ +	set_data_bits(); +	set_ctrl_bits(); +} + +/* + * Converts a parallel port pin (from -25 to 25) to data and control ports + * masks, and data and control port bits. The signal will be considered + * unconnected if it's on pin 0 or an invalid pin (<-25 or >25). + * + * Result will be used this way : + *   out(dport, in(dport) & d_val[2] | d_val[signal_state]) + *   out(cport, in(cport) & c_val[2] | c_val[signal_state]) + */ +static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val) +{ +	int d_bit, c_bit, inv; + +	d_val[0] = 0; +	c_val[0] = 0; +	d_val[1] = 0; +	c_val[1] = 0; +	d_val[2] = 0xFF; +	c_val[2] = 0xFF; + +	if (pin == 0) +		return; + +	inv = (pin < 0); +	if (inv) +		pin = -pin; + +	d_bit = 0; +	c_bit = 0; + +	switch (pin) { +	case PIN_STROBE:	/* strobe, inverted */ +		c_bit = PNL_PSTROBE; +		inv = !inv; +		break; +	case PIN_D0...PIN_D7:	/* D0 - D7 = 2 - 9 */ +		d_bit = 1 << (pin - 2); +		break; +	case PIN_AUTOLF:	/* autofeed, inverted */ +		c_bit = PNL_PAUTOLF; +		inv = !inv; +		break; +	case PIN_INITP:		/* init, direct */ +		c_bit = PNL_PINITP; +		break; +	case PIN_SELECP:	/* select_in, inverted */ +		c_bit = PNL_PSELECP; +		inv = !inv; +		break; +	default:		/* unknown pin, ignore */ +		break; +	} + +	if (c_bit) { +		c_val[2] &= ~c_bit; +		c_val[!inv] = c_bit; +	} else if (d_bit) { +		d_val[2] &= ~d_bit; +		d_val[!inv] = d_bit; +	} +} + +/* sleeps that many milliseconds with a reschedule */ +static void long_sleep(int ms) +{ +	if (in_interrupt()) +		mdelay(ms); +	else +		schedule_timeout_interruptible(msecs_to_jiffies(ms)); +} + +/* + * send a serial byte to the LCD panel. The caller is responsible for locking + * if needed. + */ +static void lcd_send_serial(int byte) +{ +	int bit; + +	/* +	 * the data bit is set on D0, and the clock on STROBE. +	 * LCD reads D0 on STROBE's rising edge. +	 */ +	for (bit = 0; bit < 8; bit++) { +		clear_bit(LCD_BIT_CL, bits);	/* CLK low */ +		panel_set_bits(); +		if (byte & 1) { +			set_bit(LCD_BIT_DA, bits); +		} else { +			clear_bit(LCD_BIT_DA, bits); +		} + +		panel_set_bits(); +		udelay(2);  /* maintain the data during 2 us before CLK up */ +		set_bit(LCD_BIT_CL, bits);	/* CLK high */ +		panel_set_bits(); +		udelay(1);  /* maintain the strobe during 1 us */ +		byte >>= 1; +	} +} + +/* turn the backlight on or off */ +static void lcd_backlight(int on) +{ +	if (lcd.pins.bl == PIN_NONE) +		return; + +	/* The backlight is activated by setting the AUTOFEED line to +5V  */ +	spin_lock_irq(&pprt_lock); +	if (on) +		set_bit(LCD_BIT_BL, bits); +	else +		clear_bit(LCD_BIT_BL, bits); +	panel_set_bits(); +	spin_unlock_irq(&pprt_lock); +} + +/* send a command to the LCD panel in serial mode */ +static void lcd_write_cmd_s(int cmd) +{ +	spin_lock_irq(&pprt_lock); +	lcd_send_serial(0x1F);	/* R/W=W, RS=0 */ +	lcd_send_serial(cmd & 0x0F); +	lcd_send_serial((cmd >> 4) & 0x0F); +	udelay(40);		/* the shortest command takes at least 40 us */ +	spin_unlock_irq(&pprt_lock); +} + +/* send data to the LCD panel in serial mode */ +static void lcd_write_data_s(int data) +{ +	spin_lock_irq(&pprt_lock); +	lcd_send_serial(0x5F);	/* R/W=W, RS=1 */ +	lcd_send_serial(data & 0x0F); +	lcd_send_serial((data >> 4) & 0x0F); +	udelay(40);		/* the shortest data takes at least 40 us */ +	spin_unlock_irq(&pprt_lock); +} + +/* send a command to the LCD panel in 8 bits parallel mode */ +static void lcd_write_cmd_p8(int cmd) +{ +	spin_lock_irq(&pprt_lock); +	/* present the data to the data port */ +	w_dtr(pprt, cmd); +	udelay(20);	/* maintain the data during 20 us before the strobe */ + +	set_bit(LCD_BIT_E, bits); +	clear_bit(LCD_BIT_RS, bits); +	clear_bit(LCD_BIT_RW, bits); +	set_ctrl_bits(); + +	udelay(40);	/* maintain the strobe during 40 us */ + +	clear_bit(LCD_BIT_E, bits); +	set_ctrl_bits(); + +	udelay(120);	/* the shortest command takes at least 120 us */ +	spin_unlock_irq(&pprt_lock); +} + +/* send data to the LCD panel in 8 bits parallel mode */ +static void lcd_write_data_p8(int data) +{ +	spin_lock_irq(&pprt_lock); +	/* present the data to the data port */ +	w_dtr(pprt, data); +	udelay(20);	/* maintain the data during 20 us before the strobe */ + +	set_bit(LCD_BIT_E, bits); +	set_bit(LCD_BIT_RS, bits); +	clear_bit(LCD_BIT_RW, bits); +	set_ctrl_bits(); + +	udelay(40);	/* maintain the strobe during 40 us */ + +	clear_bit(LCD_BIT_E, bits); +	set_ctrl_bits(); + +	udelay(45);	/* the shortest data takes at least 45 us */ +	spin_unlock_irq(&pprt_lock); +} + +/* send a command to the TI LCD panel */ +static void lcd_write_cmd_tilcd(int cmd) +{ +	spin_lock_irq(&pprt_lock); +	/* present the data to the control port */ +	w_ctr(pprt, cmd); +	udelay(60); +	spin_unlock_irq(&pprt_lock); +} + +/* send data to the TI LCD panel */ +static void lcd_write_data_tilcd(int data) +{ +	spin_lock_irq(&pprt_lock); +	/* present the data to the data port */ +	w_dtr(pprt, data); +	udelay(60); +	spin_unlock_irq(&pprt_lock); +} + +static void lcd_gotoxy(void) +{ +	lcd_write_cmd(LCD_CMD_SET_DDRAM_ADDR +		      | (lcd.addr.y ? lcd.hwidth : 0) +		      /* +		       * we force the cursor to stay at the end of the +		       * line if it wants to go farther +		       */ +		      | ((lcd.addr.x < lcd.bwidth) ? lcd.addr.x & +			 (lcd.hwidth - 1) : lcd.bwidth - 1)); +} + +static void lcd_print(char c) +{ +	if (lcd.addr.x < lcd.bwidth) { +		if (lcd_char_conv) +			c = lcd_char_conv[(unsigned char)c]; +		lcd_write_data(c); +		lcd.addr.x++; +	} +	/* prevents the cursor from wrapping onto the next line */ +	if (lcd.addr.x == lcd.bwidth) +		lcd_gotoxy(); +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_s(void) +{ +	int pos; + +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	lcd_gotoxy(); + +	spin_lock_irq(&pprt_lock); +	for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { +		lcd_send_serial(0x5F);	/* R/W=W, RS=1 */ +		lcd_send_serial(' ' & 0x0F); +		lcd_send_serial((' ' >> 4) & 0x0F); +		/* the shortest data takes at least 40 us */ +		udelay(40); +	} +	spin_unlock_irq(&pprt_lock); + +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	lcd_gotoxy(); +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_p8(void) +{ +	int pos; + +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	lcd_gotoxy(); + +	spin_lock_irq(&pprt_lock); +	for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { +		/* present the data to the data port */ +		w_dtr(pprt, ' '); + +		/* maintain the data during 20 us before the strobe */ +		udelay(20); + +		set_bit(LCD_BIT_E, bits); +		set_bit(LCD_BIT_RS, bits); +		clear_bit(LCD_BIT_RW, bits); +		set_ctrl_bits(); + +		/* maintain the strobe during 40 us */ +		udelay(40); + +		clear_bit(LCD_BIT_E, bits); +		set_ctrl_bits(); + +		/* the shortest data takes at least 45 us */ +		udelay(45); +	} +	spin_unlock_irq(&pprt_lock); + +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	lcd_gotoxy(); +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_tilcd(void) +{ +	int pos; + +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	lcd_gotoxy(); + +	spin_lock_irq(&pprt_lock); +	for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { +		/* present the data to the data port */ +		w_dtr(pprt, ' '); +		udelay(60); +	} + +	spin_unlock_irq(&pprt_lock); + +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	lcd_gotoxy(); +} + +/* clears the display and resets X/Y */ +static void lcd_clear_display(void) +{ +	lcd_write_cmd(LCD_CMD_DISPLAY_CLEAR); +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	/* we must wait a few milliseconds (15) */ +	long_sleep(15); +} + +static void lcd_init_display(void) +{ +	lcd.flags = ((lcd.height > 1) ? LCD_FLAG_N : 0) +	    | LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B; + +	long_sleep(20);		/* wait 20 ms after power-up for the paranoid */ + +	/* 8bits, 1 line, small fonts; let's do it 3 times */ +	lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); +	long_sleep(10); +	lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); +	long_sleep(10); +	lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); +	long_sleep(10); + +	/* set font height and lines number */ +	lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS +		      | ((lcd.flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) +		      | ((lcd.flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0) +	    ); +	long_sleep(10); + +	/* display off, cursor off, blink off */ +	lcd_write_cmd(LCD_CMD_DISPLAY_CTRL); +	long_sleep(10); + +	lcd_write_cmd(LCD_CMD_DISPLAY_CTRL	/* set display mode */ +		      | ((lcd.flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) +		      | ((lcd.flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) +		      | ((lcd.flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0) +	    ); + +	lcd_backlight((lcd.flags & LCD_FLAG_L) ? 1 : 0); + +	long_sleep(10); + +	/* entry mode set : increment, cursor shifting */ +	lcd_write_cmd(LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); + +	lcd_clear_display(); +} + +/* + * These are the file operation function for user access to /dev/lcd + * This function can also be called from inside the kernel, by + * setting file and ppos to NULL. + * + */ + +static inline int handle_lcd_special_code(void) +{ +	/* LCD special codes */ + +	int processed = 0; + +	char *esc = lcd.esc_seq.buf + 2; +	int oldflags = lcd.flags; + +	/* check for display mode flags */ +	switch (*esc) { +	case 'D':	/* Display ON */ +		lcd.flags |= LCD_FLAG_D; +		processed = 1; +		break; +	case 'd':	/* Display OFF */ +		lcd.flags &= ~LCD_FLAG_D; +		processed = 1; +		break; +	case 'C':	/* Cursor ON */ +		lcd.flags |= LCD_FLAG_C; +		processed = 1; +		break; +	case 'c':	/* Cursor OFF */ +		lcd.flags &= ~LCD_FLAG_C; +		processed = 1; +		break; +	case 'B':	/* Blink ON */ +		lcd.flags |= LCD_FLAG_B; +		processed = 1; +		break; +	case 'b':	/* Blink OFF */ +		lcd.flags &= ~LCD_FLAG_B; +		processed = 1; +		break; +	case '+':	/* Back light ON */ +		lcd.flags |= LCD_FLAG_L; +		processed = 1; +		break; +	case '-':	/* Back light OFF */ +		lcd.flags &= ~LCD_FLAG_L; +		processed = 1; +		break; +	case '*': +		/* flash back light using the keypad timer */ +		if (scan_timer.function) { +			if (lcd.light_tempo == 0 && +			    ((lcd.flags & LCD_FLAG_L) == 0)) +				lcd_backlight(1); +			lcd.light_tempo = FLASH_LIGHT_TEMPO; +		} +		processed = 1; +		break; +	case 'f':	/* Small Font */ +		lcd.flags &= ~LCD_FLAG_F; +		processed = 1; +		break; +	case 'F':	/* Large Font */ +		lcd.flags |= LCD_FLAG_F; +		processed = 1; +		break; +	case 'n':	/* One Line */ +		lcd.flags &= ~LCD_FLAG_N; +		processed = 1; +		break; +	case 'N':	/* Two Lines */ +		lcd.flags |= LCD_FLAG_N; +		break; +	case 'l':	/* Shift Cursor Left */ +		if (lcd.addr.x > 0) { +			/* back one char if not at end of line */ +			if (lcd.addr.x < lcd.bwidth) +				lcd_write_cmd(LCD_CMD_SHIFT); +			lcd.addr.x--; +		} +		processed = 1; +		break; +	case 'r':	/* shift cursor right */ +		if (lcd.addr.x < lcd.width) { +			/* allow the cursor to pass the end of the line */ +			if (lcd.addr.x < (lcd.bwidth - 1)) +				lcd_write_cmd(LCD_CMD_SHIFT | +						LCD_CMD_SHIFT_RIGHT); +			lcd.addr.x++; +		} +		processed = 1; +		break; +	case 'L':	/* shift display left */ +		lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); +		processed = 1; +		break; +	case 'R':	/* shift display right */ +		lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | +				LCD_CMD_SHIFT_RIGHT); +		processed = 1; +		break; +	case 'k': {	/* kill end of line */ +		int x; + +		for (x = lcd.addr.x; x < lcd.bwidth; x++) +			lcd_write_data(' '); + +		/* restore cursor position */ +		lcd_gotoxy(); +		processed = 1; +		break; +	} +	case 'I':	/* reinitialize display */ +		lcd_init_display(); +		processed = 1; +		break; +	case 'G': { +		/* Generator : LGcxxxxx...xx; must have <c> between '0' +		 * and '7', representing the numerical ASCII code of the +		 * redefined character, and <xx...xx> a sequence of 16 +		 * hex digits representing 8 bytes for each character. +		 * Most LCDs will only use 5 lower bits of the 7 first +		 * bytes. +		 */ + +		unsigned char cgbytes[8]; +		unsigned char cgaddr; +		int cgoffset; +		int shift; +		char value; +		int addr; + +		if (!strchr(esc, ';')) +			break; + +		esc++; + +		cgaddr = *(esc++) - '0'; +		if (cgaddr > 7) { +			processed = 1; +			break; +		} + +		cgoffset = 0; +		shift = 0; +		value = 0; +		while (*esc && cgoffset < 8) { +			shift ^= 4; +			if (*esc >= '0' && *esc <= '9') { +				value |= (*esc - '0') << shift; +			} else if (*esc >= 'A' && *esc <= 'Z') { +				value |= (*esc - 'A' + 10) << shift; +			} else if (*esc >= 'a' && *esc <= 'z') { +				value |= (*esc - 'a' + 10) << shift; +			} else { +				esc++; +				continue; +			} + +			if (shift == 0) { +				cgbytes[cgoffset++] = value; +				value = 0; +			} + +			esc++; +		} + +		lcd_write_cmd(LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); +		for (addr = 0; addr < cgoffset; addr++) +			lcd_write_data(cgbytes[addr]); + +		/* ensures that we stop writing to CGRAM */ +		lcd_gotoxy(); +		processed = 1; +		break; +	} +	case 'x':	/* gotoxy : LxXXX[yYYY]; */ +	case 'y':	/* gotoxy : LyYYY[xXXX]; */ +		if (!strchr(esc, ';')) +			break; + +		while (*esc) { +			if (*esc == 'x') { +				esc++; +				if (kstrtoul(esc, 10, &lcd.addr.x) < 0) +					break; +			} else if (*esc == 'y') { +				esc++; +				if (kstrtoul(esc, 10, &lcd.addr.y) < 0) +					break; +			} else { +				break; +			} +		} + +		lcd_gotoxy(); +		processed = 1; +		break; +	} + +	/* TODO: This indent party here got ugly, clean it! */ +	/* Check whether one flag was changed */ +	if (oldflags != lcd.flags) { +		/* check whether one of B,C,D flags were changed */ +		if ((oldflags ^ lcd.flags) & +		    (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) +			/* set display mode */ +			lcd_write_cmd(LCD_CMD_DISPLAY_CTRL +				      | ((lcd.flags & LCD_FLAG_D) +						      ? LCD_CMD_DISPLAY_ON : 0) +				      | ((lcd.flags & LCD_FLAG_C) +						      ? LCD_CMD_CURSOR_ON : 0) +				      | ((lcd.flags & LCD_FLAG_B) +						      ? LCD_CMD_BLINK_ON : 0)); +		/* check whether one of F,N flags was changed */ +		else if ((oldflags ^ lcd.flags) & (LCD_FLAG_F | LCD_FLAG_N)) +			lcd_write_cmd(LCD_CMD_FUNCTION_SET +				      | LCD_CMD_DATA_LEN_8BITS +				      | ((lcd.flags & LCD_FLAG_F) +						      ? LCD_CMD_TWO_LINES : 0) +				      | ((lcd.flags & LCD_FLAG_N) +						      ? LCD_CMD_FONT_5X10_DOTS +								      : 0)); +		/* check whether L flag was changed */ +		else if ((oldflags ^ lcd.flags) & (LCD_FLAG_L)) { +			if (lcd.flags & (LCD_FLAG_L)) +				lcd_backlight(1); +			else if (lcd.light_tempo == 0) +				/* +				 * switch off the light only when the tempo +				 * lighting is gone +				 */ +				lcd_backlight(0); +		} +	} + +	return processed; +} + +static void lcd_write_char(char c) +{ +	/* first, we'll test if we're in escape mode */ +	if ((c != '\n') && lcd.esc_seq.len >= 0) { +		/* yes, let's add this char to the buffer */ +		lcd.esc_seq.buf[lcd.esc_seq.len++] = c; +		lcd.esc_seq.buf[lcd.esc_seq.len] = 0; +	} else { +		/* aborts any previous escape sequence */ +		lcd.esc_seq.len = -1; + +		switch (c) { +		case LCD_ESCAPE_CHAR: +			/* start of an escape sequence */ +			lcd.esc_seq.len = 0; +			lcd.esc_seq.buf[lcd.esc_seq.len] = 0; +			break; +		case '\b': +			/* go back one char and clear it */ +			if (lcd.addr.x > 0) { +				/* +				 * check if we're not at the +				 * end of the line +				 */ +				if (lcd.addr.x < lcd.bwidth) +					/* back one char */ +					lcd_write_cmd(LCD_CMD_SHIFT); +				lcd.addr.x--; +			} +			/* replace with a space */ +			lcd_write_data(' '); +			/* back one char again */ +			lcd_write_cmd(LCD_CMD_SHIFT); +			break; +		case '\014': +			/* quickly clear the display */ +			lcd_clear_fast(); +			break; +		case '\n': +			/* +			 * flush the remainder of the current line and +			 * go to the beginning of the next line +			 */ +			for (; lcd.addr.x < lcd.bwidth; lcd.addr.x++) +				lcd_write_data(' '); +			lcd.addr.x = 0; +			lcd.addr.y = (lcd.addr.y + 1) % lcd.height; +			lcd_gotoxy(); +			break; +		case '\r': +			/* go to the beginning of the same line */ +			lcd.addr.x = 0; +			lcd_gotoxy(); +			break; +		case '\t': +			/* print a space instead of the tab */ +			lcd_print(' '); +			break; +		default: +			/* simply print this char */ +			lcd_print(c); +			break; +		} +	} + +	/* +	 * now we'll see if we're in an escape mode and if the current +	 * escape sequence can be understood. +	 */ +	if (lcd.esc_seq.len >= 2) { +		int processed = 0; + +		if (!strcmp(lcd.esc_seq.buf, "[2J")) { +			/* clear the display */ +			lcd_clear_fast(); +			processed = 1; +		} else if (!strcmp(lcd.esc_seq.buf, "[H")) { +			/* cursor to home */ +			lcd.addr.x = 0; +			lcd.addr.y = 0; +			lcd_gotoxy(); +			processed = 1; +		} +		/* codes starting with ^[[L */ +		else if ((lcd.esc_seq.len >= 3) && +			 (lcd.esc_seq.buf[0] == '[') && +			 (lcd.esc_seq.buf[1] == 'L')) { +			processed = handle_lcd_special_code(); +		} + +		/* LCD special escape codes */ +		/* +		 * flush the escape sequence if it's been processed +		 * or if it is getting too long. +		 */ +		if (processed || (lcd.esc_seq.len >= LCD_ESCAPE_LEN)) +			lcd.esc_seq.len = -1; +	} /* escape codes */ +} + +static ssize_t lcd_write(struct file *file, +			 const char __user *buf, size_t count, loff_t *ppos) +{ +	const char __user *tmp = buf; +	char c; + +	for (; count-- > 0; (*ppos)++, tmp++) { +		if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) +			/* +			 * let's be a little nice with other processes +			 * that need some CPU +			 */ +			schedule(); + +		if (get_user(c, tmp)) +			return -EFAULT; + +		lcd_write_char(c); +	} + +	return tmp - buf; +} + +static int lcd_open(struct inode *inode, struct file *file) +{ +	if (!atomic_dec_and_test(&lcd_available)) +		return -EBUSY;	/* open only once at a time */ + +	if (file->f_mode & FMODE_READ)	/* device is write-only */ +		return -EPERM; + +	if (lcd.must_clear) { +		lcd_clear_display(); +		lcd.must_clear = false; +	} +	return nonseekable_open(inode, file); +} + +static int lcd_release(struct inode *inode, struct file *file) +{ +	atomic_inc(&lcd_available); +	return 0; +} + +static const struct file_operations lcd_fops = { +	.write   = lcd_write, +	.open    = lcd_open, +	.release = lcd_release, +	.llseek  = no_llseek, +}; + +static struct miscdevice lcd_dev = { +	.minor	= LCD_MINOR, +	.name	= "lcd", +	.fops	= &lcd_fops, +}; + +/* public function usable from the kernel for any purpose */ +static void panel_lcd_print(const char *s) +{ +	const char *tmp = s; +	int count = strlen(s); + +	if (lcd.enabled && lcd.initialized) { +		for (; count-- > 0; tmp++) { +			if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) +				/* +				 * let's be a little nice with other processes +				 * that need some CPU +				 */ +				schedule(); + +			lcd_write_char(*tmp); +		} +	} +} + +/* initialize the LCD driver */ +static void lcd_init(void) +{ +	switch (selected_lcd_type) { +	case LCD_TYPE_OLD: +		/* parallel mode, 8 bits */ +		lcd.proto = LCD_PROTO_PARALLEL; +		lcd.charset = LCD_CHARSET_NORMAL; +		lcd.pins.e = PIN_STROBE; +		lcd.pins.rs = PIN_AUTOLF; + +		lcd.width = 40; +		lcd.bwidth = 40; +		lcd.hwidth = 64; +		lcd.height = 2; +		break; +	case LCD_TYPE_KS0074: +		/* serial mode, ks0074 */ +		lcd.proto = LCD_PROTO_SERIAL; +		lcd.charset = LCD_CHARSET_KS0074; +		lcd.pins.bl = PIN_AUTOLF; +		lcd.pins.cl = PIN_STROBE; +		lcd.pins.da = PIN_D0; + +		lcd.width = 16; +		lcd.bwidth = 40; +		lcd.hwidth = 16; +		lcd.height = 2; +		break; +	case LCD_TYPE_NEXCOM: +		/* parallel mode, 8 bits, generic */ +		lcd.proto = LCD_PROTO_PARALLEL; +		lcd.charset = LCD_CHARSET_NORMAL; +		lcd.pins.e = PIN_AUTOLF; +		lcd.pins.rs = PIN_SELECP; +		lcd.pins.rw = PIN_INITP; + +		lcd.width = 16; +		lcd.bwidth = 40; +		lcd.hwidth = 64; +		lcd.height = 2; +		break; +	case LCD_TYPE_CUSTOM: +		/* customer-defined */ +		lcd.proto = DEFAULT_LCD_PROTO; +		lcd.charset = DEFAULT_LCD_CHARSET; +		/* default geometry will be set later */ +		break; +	case LCD_TYPE_HANTRONIX: +		/* parallel mode, 8 bits, hantronix-like */ +	default: +		lcd.proto = LCD_PROTO_PARALLEL; +		lcd.charset = LCD_CHARSET_NORMAL; +		lcd.pins.e = PIN_STROBE; +		lcd.pins.rs = PIN_SELECP; + +		lcd.width = 16; +		lcd.bwidth = 40; +		lcd.hwidth = 64; +		lcd.height = 2; +		break; +	} + +	/* Overwrite with module params set on loading */ +	if (lcd_height != NOT_SET) +		lcd.height = lcd_height; +	if (lcd_width != NOT_SET) +		lcd.width = lcd_width; +	if (lcd_bwidth != NOT_SET) +		lcd.bwidth = lcd_bwidth; +	if (lcd_hwidth != NOT_SET) +		lcd.hwidth = lcd_hwidth; +	if (lcd_charset != NOT_SET) +		lcd.charset = lcd_charset; +	if (lcd_proto != NOT_SET) +		lcd.proto = lcd_proto; +	if (lcd_e_pin != PIN_NOT_SET) +		lcd.pins.e = lcd_e_pin; +	if (lcd_rs_pin != PIN_NOT_SET) +		lcd.pins.rs = lcd_rs_pin; +	if (lcd_rw_pin != PIN_NOT_SET) +		lcd.pins.rw = lcd_rw_pin; +	if (lcd_cl_pin != PIN_NOT_SET) +		lcd.pins.cl = lcd_cl_pin; +	if (lcd_da_pin != PIN_NOT_SET) +		lcd.pins.da = lcd_da_pin; +	if (lcd_bl_pin != PIN_NOT_SET) +		lcd.pins.bl = lcd_bl_pin; + +	/* this is used to catch wrong and default values */ +	if (lcd.width <= 0) +		lcd.width = DEFAULT_LCD_WIDTH; +	if (lcd.bwidth <= 0) +		lcd.bwidth = DEFAULT_LCD_BWIDTH; +	if (lcd.hwidth <= 0) +		lcd.hwidth = DEFAULT_LCD_HWIDTH; +	if (lcd.height <= 0) +		lcd.height = DEFAULT_LCD_HEIGHT; + +	if (lcd.proto == LCD_PROTO_SERIAL) {	/* SERIAL */ +		lcd_write_cmd = lcd_write_cmd_s; +		lcd_write_data = lcd_write_data_s; +		lcd_clear_fast = lcd_clear_fast_s; + +		if (lcd.pins.cl == PIN_NOT_SET) +			lcd.pins.cl = DEFAULT_LCD_PIN_SCL; +		if (lcd.pins.da == PIN_NOT_SET) +			lcd.pins.da = DEFAULT_LCD_PIN_SDA; + +	} else if (lcd.proto == LCD_PROTO_PARALLEL) {	/* PARALLEL */ +		lcd_write_cmd = lcd_write_cmd_p8; +		lcd_write_data = lcd_write_data_p8; +		lcd_clear_fast = lcd_clear_fast_p8; + +		if (lcd.pins.e == PIN_NOT_SET) +			lcd.pins.e = DEFAULT_LCD_PIN_E; +		if (lcd.pins.rs == PIN_NOT_SET) +			lcd.pins.rs = DEFAULT_LCD_PIN_RS; +		if (lcd.pins.rw == PIN_NOT_SET) +			lcd.pins.rw = DEFAULT_LCD_PIN_RW; +	} else { +		lcd_write_cmd = lcd_write_cmd_tilcd; +		lcd_write_data = lcd_write_data_tilcd; +		lcd_clear_fast = lcd_clear_fast_tilcd; +	} + +	if (lcd.pins.bl == PIN_NOT_SET) +		lcd.pins.bl = DEFAULT_LCD_PIN_BL; + +	if (lcd.pins.e == PIN_NOT_SET) +		lcd.pins.e = PIN_NONE; +	if (lcd.pins.rs == PIN_NOT_SET) +		lcd.pins.rs = PIN_NONE; +	if (lcd.pins.rw == PIN_NOT_SET) +		lcd.pins.rw = PIN_NONE; +	if (lcd.pins.bl == PIN_NOT_SET) +		lcd.pins.bl = PIN_NONE; +	if (lcd.pins.cl == PIN_NOT_SET) +		lcd.pins.cl = PIN_NONE; +	if (lcd.pins.da == PIN_NOT_SET) +		lcd.pins.da = PIN_NONE; + +	if (lcd.charset == NOT_SET) +		lcd.charset = DEFAULT_LCD_CHARSET; + +	if (lcd.charset == LCD_CHARSET_KS0074) +		lcd_char_conv = lcd_char_conv_ks0074; +	else +		lcd_char_conv = NULL; + +	if (lcd.pins.bl != PIN_NONE) +		init_scan_timer(); + +	pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E], +		    lcd_bits[LCD_PORT_C][LCD_BIT_E]); +	pin_to_bits(lcd.pins.rs, lcd_bits[LCD_PORT_D][LCD_BIT_RS], +		    lcd_bits[LCD_PORT_C][LCD_BIT_RS]); +	pin_to_bits(lcd.pins.rw, lcd_bits[LCD_PORT_D][LCD_BIT_RW], +		    lcd_bits[LCD_PORT_C][LCD_BIT_RW]); +	pin_to_bits(lcd.pins.bl, lcd_bits[LCD_PORT_D][LCD_BIT_BL], +		    lcd_bits[LCD_PORT_C][LCD_BIT_BL]); +	pin_to_bits(lcd.pins.cl, lcd_bits[LCD_PORT_D][LCD_BIT_CL], +		    lcd_bits[LCD_PORT_C][LCD_BIT_CL]); +	pin_to_bits(lcd.pins.da, lcd_bits[LCD_PORT_D][LCD_BIT_DA], +		    lcd_bits[LCD_PORT_C][LCD_BIT_DA]); + +	/* +	 * before this line, we must NOT send anything to the display. +	 * Since lcd_init_display() needs to write data, we have to +	 * enable mark the LCD initialized just before. +	 */ +	lcd.initialized = true; +	lcd_init_display(); + +	/* display a short message */ +#ifdef CONFIG_PANEL_CHANGE_MESSAGE +#ifdef CONFIG_PANEL_BOOT_MESSAGE +	panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); +#endif +#else +	panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\nPanel-" +			PANEL_VERSION); +#endif +	lcd.addr.x = 0; +	lcd.addr.y = 0; +	/* clear the display on the next device opening */ +	lcd.must_clear = true; +	lcd_gotoxy(); +} + +/* + * These are the file operation function for user access to /dev/keypad + */ + +static ssize_t keypad_read(struct file *file, +			   char __user *buf, size_t count, loff_t *ppos) +{ +	unsigned i = *ppos; +	char __user *tmp = buf; + +	if (keypad_buflen == 0) { +		if (file->f_flags & O_NONBLOCK) +			return -EAGAIN; + +		if (wait_event_interruptible(keypad_read_wait, +					     keypad_buflen != 0)) +			return -EINTR; +	} + +	for (; count-- > 0 && (keypad_buflen > 0); +	     ++i, ++tmp, --keypad_buflen) { +		put_user(keypad_buffer[keypad_start], tmp); +		keypad_start = (keypad_start + 1) % KEYPAD_BUFFER; +	} +	*ppos = i; + +	return tmp - buf; +} + +static int keypad_open(struct inode *inode, struct file *file) +{ +	if (!atomic_dec_and_test(&keypad_available)) +		return -EBUSY;	/* open only once at a time */ + +	if (file->f_mode & FMODE_WRITE)	/* device is read-only */ +		return -EPERM; + +	keypad_buflen = 0;	/* flush the buffer on opening */ +	return 0; +} + +static int keypad_release(struct inode *inode, struct file *file) +{ +	atomic_inc(&keypad_available); +	return 0; +} + +static const struct file_operations keypad_fops = { +	.read    = keypad_read,		/* read */ +	.open    = keypad_open,		/* open */ +	.release = keypad_release,	/* close */ +	.llseek  = default_llseek, +}; + +static struct miscdevice keypad_dev = { +	.minor	= KEYPAD_MINOR, +	.name	= "keypad", +	.fops	= &keypad_fops, +}; + +static void keypad_send_key(const char *string, int max_len) +{ +	/* send the key to the device only if a process is attached to it. */ +	if (!atomic_read(&keypad_available)) { +		while (max_len-- && keypad_buflen < KEYPAD_BUFFER && *string) { +			keypad_buffer[(keypad_start + keypad_buflen++) % +				      KEYPAD_BUFFER] = *string++; +		} +		wake_up_interruptible(&keypad_read_wait); +	} +} + +/* this function scans all the bits involving at least one logical signal, + * and puts the results in the bitfield "phys_read" (one bit per established + * contact), and sets "phys_read_prev" to "phys_read". + * + * Note: to debounce input signals, we will only consider as switched a signal + * which is stable across 2 measures. Signals which are different between two + * reads will be kept as they previously were in their logical form (phys_prev). + * A signal which has just switched will have a 1 in + * (phys_read ^ phys_read_prev). + */ +static void phys_scan_contacts(void) +{ +	int bit, bitval; +	char oldval; +	char bitmask; +	char gndmask; + +	phys_prev = phys_curr; +	phys_read_prev = phys_read; +	phys_read = 0;		/* flush all signals */ + +	/* keep track of old value, with all outputs disabled */ +	oldval = r_dtr(pprt) | scan_mask_o; +	/* activate all keyboard outputs (active low) */ +	w_dtr(pprt, oldval & ~scan_mask_o); + +	/* will have a 1 for each bit set to gnd */ +	bitmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; +	/* disable all matrix signals */ +	w_dtr(pprt, oldval); + +	/* now that all outputs are cleared, the only active input bits are +	 * directly connected to the ground +	 */ + +	/* 1 for each grounded input */ +	gndmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; + +	/* grounded inputs are signals 40-44 */ +	phys_read |= (__u64)gndmask << 40; + +	if (bitmask != gndmask) { +		/* +		 * since clearing the outputs changed some inputs, we know +		 * that some input signals are currently tied to some outputs. +		 * So we'll scan them. +		 */ +		for (bit = 0; bit < 8; bit++) { +			bitval = BIT(bit); + +			if (!(scan_mask_o & bitval))	/* skip unused bits */ +				continue; + +			w_dtr(pprt, oldval & ~bitval);	/* enable this output */ +			bitmask = PNL_PINPUT(r_str(pprt)) & ~gndmask; +			phys_read |= (__u64)bitmask << (5 * bit); +		} +		w_dtr(pprt, oldval);	/* disable all outputs */ +	} +	/* +	 * this is easy: use old bits when they are flapping, +	 * use new ones when stable +	 */ +	phys_curr = (phys_prev & (phys_read ^ phys_read_prev)) | +		    (phys_read & ~(phys_read ^ phys_read_prev)); +} + +static inline int input_state_high(struct logical_input *input) +{ +#if 0 +	/* FIXME: +	 * this is an invalid test. It tries to catch +	 * transitions from single-key to multiple-key, but +	 * doesn't take into account the contacts polarity. +	 * The only solution to the problem is to parse keys +	 * from the most complex to the simplest combinations, +	 * and mark them as 'caught' once a combination +	 * matches, then unmatch it for all other ones. +	 */ + +	/* try to catch dangerous transitions cases : +	 * someone adds a bit, so this signal was a false +	 * positive resulting from a transition. We should +	 * invalidate the signal immediately and not call the +	 * release function. +	 * eg: 0 -(press A)-> A -(press B)-> AB : don't match A's release. +	 */ +	if (((phys_prev & input->mask) == input->value) && +	    ((phys_curr & input->mask) >  input->value)) { +		input->state = INPUT_ST_LOW; /* invalidate */ +		return 1; +	} +#endif + +	if ((phys_curr & input->mask) == input->value) { +		if ((input->type == INPUT_TYPE_STD) && +		    (input->high_timer == 0)) { +			input->high_timer++; +			if (input->u.std.press_fct) +				input->u.std.press_fct(input->u.std.press_data); +		} else if (input->type == INPUT_TYPE_KBD) { +			/* will turn on the light */ +			keypressed = 1; + +			if (input->high_timer == 0) { +				char *press_str = input->u.kbd.press_str; + +				if (press_str[0]) { +					int s = sizeof(input->u.kbd.press_str); + +					keypad_send_key(press_str, s); +				} +			} + +			if (input->u.kbd.repeat_str[0]) { +				char *repeat_str = input->u.kbd.repeat_str; + +				if (input->high_timer >= KEYPAD_REP_START) { +					int s = sizeof(input->u.kbd.repeat_str); + +					input->high_timer -= KEYPAD_REP_DELAY; +					keypad_send_key(repeat_str, s); +				} +				/* we will need to come back here soon */ +				inputs_stable = 0; +			} + +			if (input->high_timer < 255) +				input->high_timer++; +		} +		return 1; +	} + +	/* else signal falling down. Let's fall through. */ +	input->state = INPUT_ST_FALLING; +	input->fall_timer = 0; + +	return 0; +} + +static inline void input_state_falling(struct logical_input *input) +{ +#if 0 +	/* FIXME !!! same comment as in input_state_high */ +	if (((phys_prev & input->mask) == input->value) && +	    ((phys_curr & input->mask) >  input->value)) { +		input->state = INPUT_ST_LOW;	/* invalidate */ +		return; +	} +#endif + +	if ((phys_curr & input->mask) == input->value) { +		if (input->type == INPUT_TYPE_KBD) { +			/* will turn on the light */ +			keypressed = 1; + +			if (input->u.kbd.repeat_str[0]) { +				char *repeat_str = input->u.kbd.repeat_str; + +				if (input->high_timer >= KEYPAD_REP_START) { +					int s = sizeof(input->u.kbd.repeat_str); + +					input->high_timer -= KEYPAD_REP_DELAY; +					keypad_send_key(repeat_str, s); +				} +				/* we will need to come back here soon */ +				inputs_stable = 0; +			} + +			if (input->high_timer < 255) +				input->high_timer++; +		} +		input->state = INPUT_ST_HIGH; +	} else if (input->fall_timer >= input->fall_time) { +		/* call release event */ +		if (input->type == INPUT_TYPE_STD) { +			void (*release_fct)(int) = input->u.std.release_fct; + +			if (release_fct) +				release_fct(input->u.std.release_data); +		} else if (input->type == INPUT_TYPE_KBD) { +			char *release_str = input->u.kbd.release_str; + +			if (release_str[0]) { +				int s = sizeof(input->u.kbd.release_str); + +				keypad_send_key(release_str, s); +			} +		} + +		input->state = INPUT_ST_LOW; +	} else { +		input->fall_timer++; +		inputs_stable = 0; +	} +} + +static void panel_process_inputs(void) +{ +	struct list_head *item; +	struct logical_input *input; + +	keypressed = 0; +	inputs_stable = 1; +	list_for_each(item, &logical_inputs) { +		input = list_entry(item, struct logical_input, list); + +		switch (input->state) { +		case INPUT_ST_LOW: +			if ((phys_curr & input->mask) != input->value) +				break; +			/* if all needed ones were already set previously, +			 * this means that this logical signal has been +			 * activated by the releasing of another combined +			 * signal, so we don't want to match. +			 * eg: AB -(release B)-> A -(release A)-> 0 : +			 *     don't match A. +			 */ +			if ((phys_prev & input->mask) == input->value) +				break; +			input->rise_timer = 0; +			input->state = INPUT_ST_RISING; +			/* no break here, fall through */ +		case INPUT_ST_RISING: +			if ((phys_curr & input->mask) != input->value) { +				input->state = INPUT_ST_LOW; +				break; +			} +			if (input->rise_timer < input->rise_time) { +				inputs_stable = 0; +				input->rise_timer++; +				break; +			} +			input->high_timer = 0; +			input->state = INPUT_ST_HIGH; +			/* no break here, fall through */ +		case INPUT_ST_HIGH: +			if (input_state_high(input)) +				break; +			/* no break here, fall through */ +		case INPUT_ST_FALLING: +			input_state_falling(input); +		} +	} +} + +static void panel_scan_timer(void) +{ +	if (keypad.enabled && keypad_initialized) { +		if (spin_trylock_irq(&pprt_lock)) { +			phys_scan_contacts(); + +			/* no need for the parport anymore */ +			spin_unlock_irq(&pprt_lock); +		} + +		if (!inputs_stable || phys_curr != phys_prev) +			panel_process_inputs(); +	} + +	if (lcd.enabled && lcd.initialized) { +		if (keypressed) { +			if (lcd.light_tempo == 0 && +			    ((lcd.flags & LCD_FLAG_L) == 0)) +				lcd_backlight(1); +			lcd.light_tempo = FLASH_LIGHT_TEMPO; +		} else if (lcd.light_tempo > 0) { +			lcd.light_tempo--; +			if (lcd.light_tempo == 0 && +			    ((lcd.flags & LCD_FLAG_L) == 0)) +				lcd_backlight(0); +		} +	} + +	mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME); +} + +static void init_scan_timer(void) +{ +	if (scan_timer.function) +		return;		/* already started */ + +	setup_timer(&scan_timer, (void *)&panel_scan_timer, 0); +	scan_timer.expires = jiffies + INPUT_POLL_TIME; +	add_timer(&scan_timer); +} + +/* converts a name of the form "({BbAaPpSsEe}{01234567-})*" to a series of bits. + * if <omask> or <imask> are non-null, they will be or'ed with the bits + * corresponding to out and in bits respectively. + * returns 1 if ok, 0 if error (in which case, nothing is written). + */ +static u8 input_name2mask(const char *name, __u64 *mask, __u64 *value, +			  u8 *imask, u8 *omask) +{ +	const char sigtab[] = "EeSsPpAaBb"; +	u8 im, om; +	__u64 m, v; + +	om = 0; +	im = 0; +	m = 0ULL; +	v = 0ULL; +	while (*name) { +		int in, out, bit, neg; +		const char *idx; + +		idx = strchr(sigtab, *name); +		if (!idx) +			return 0;	/* input name not found */ + +		in = idx - sigtab; +		neg = (in & 1);	/* odd (lower) names are negated */ +		in >>= 1; +		im |= BIT(in); + +		name++; +		if (*name >= '0' && *name <= '7') { +			out = *name - '0'; +			om |= BIT(out); +		} else if (*name == '-') { +			out = 8; +		} else { +			return 0;	/* unknown bit name */ +		} + +		bit = (out * 5) + in; + +		m |= 1ULL << bit; +		if (!neg) +			v |= 1ULL << bit; +		name++; +	} +	*mask = m; +	*value = v; +	if (imask) +		*imask |= im; +	if (omask) +		*omask |= om; +	return 1; +} + +/* tries to bind a key to the signal name <name>. The key will send the + * strings <press>, <repeat>, <release> for these respective events. + * Returns the pointer to the new key if ok, NULL if the key could not be bound. + */ +static struct logical_input *panel_bind_key(const char *name, const char *press, +					    const char *repeat, +					    const char *release) +{ +	struct logical_input *key; + +	key = kzalloc(sizeof(*key), GFP_KERNEL); +	if (!key) +		return NULL; + +	if (!input_name2mask(name, &key->mask, &key->value, &scan_mask_i, +			     &scan_mask_o)) { +		kfree(key); +		return NULL; +	} + +	key->type = INPUT_TYPE_KBD; +	key->state = INPUT_ST_LOW; +	key->rise_time = 1; +	key->fall_time = 1; + +	strncpy(key->u.kbd.press_str, press, sizeof(key->u.kbd.press_str)); +	strncpy(key->u.kbd.repeat_str, repeat, sizeof(key->u.kbd.repeat_str)); +	strncpy(key->u.kbd.release_str, release, +		sizeof(key->u.kbd.release_str)); +	list_add(&key->list, &logical_inputs); +	return key; +} + +#if 0 +/* tries to bind a callback function to the signal name <name>. The function + * <press_fct> will be called with the <press_data> arg when the signal is + * activated, and so on for <release_fct>/<release_data> + * Returns the pointer to the new signal if ok, NULL if the signal could not + * be bound. + */ +static struct logical_input *panel_bind_callback(char *name, +						 void (*press_fct)(int), +						 int press_data, +						 void (*release_fct)(int), +						 int release_data) +{ +	struct logical_input *callback; + +	callback = kmalloc(sizeof(*callback), GFP_KERNEL); +	if (!callback) +		return NULL; + +	memset(callback, 0, sizeof(struct logical_input)); +	if (!input_name2mask(name, &callback->mask, &callback->value, +			     &scan_mask_i, &scan_mask_o)) +		return NULL; + +	callback->type = INPUT_TYPE_STD; +	callback->state = INPUT_ST_LOW; +	callback->rise_time = 1; +	callback->fall_time = 1; +	callback->u.std.press_fct = press_fct; +	callback->u.std.press_data = press_data; +	callback->u.std.release_fct = release_fct; +	callback->u.std.release_data = release_data; +	list_add(&callback->list, &logical_inputs); +	return callback; +} +#endif + +static void keypad_init(void) +{ +	int keynum; + +	init_waitqueue_head(&keypad_read_wait); +	keypad_buflen = 0;	/* flushes any eventual noisy keystroke */ + +	/* Let's create all known keys */ + +	for (keynum = 0; keypad_profile[keynum][0][0]; keynum++) { +		panel_bind_key(keypad_profile[keynum][0], +			       keypad_profile[keynum][1], +			       keypad_profile[keynum][2], +			       keypad_profile[keynum][3]); +	} + +	init_scan_timer(); +	keypad_initialized = 1; +} + +/**************************************************/ +/* device initialization                          */ +/**************************************************/ + +static int panel_notify_sys(struct notifier_block *this, unsigned long code, +			    void *unused) +{ +	if (lcd.enabled && lcd.initialized) { +		switch (code) { +		case SYS_DOWN: +			panel_lcd_print +			    ("\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); +			break; +		case SYS_HALT: +			panel_lcd_print +			    ("\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); +			break; +		case SYS_POWER_OFF: +			panel_lcd_print("\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); +			break; +		default: +			break; +		} +	} +	return NOTIFY_DONE; +} + +static struct notifier_block panel_notifier = { +	panel_notify_sys, +	NULL, +	0 +}; + +static void panel_attach(struct parport *port) +{ +	struct pardev_cb panel_cb; + +	if (port->number != parport) +		return; + +	if (pprt) { +		pr_err("%s: port->number=%d parport=%d, already registered!\n", +		       __func__, port->number, parport); +		return; +	} + +	memset(&panel_cb, 0, sizeof(panel_cb)); +	panel_cb.private = &pprt; +	/* panel_cb.flags = 0 should be PARPORT_DEV_EXCL? */ + +	pprt = parport_register_dev_model(port, "panel", &panel_cb, 0); +	if (!pprt) { +		pr_err("%s: port->number=%d parport=%d, parport_register_device() failed\n", +		       __func__, port->number, parport); +		return; +	} + +	if (parport_claim(pprt)) { +		pr_err("could not claim access to parport%d. Aborting.\n", +		       parport); +		goto err_unreg_device; +	} + +	/* must init LCD first, just in case an IRQ from the keypad is +	 * generated at keypad init +	 */ +	if (lcd.enabled) { +		lcd_init(); +		if (misc_register(&lcd_dev)) +			goto err_unreg_device; +	} + +	if (keypad.enabled) { +		keypad_init(); +		if (misc_register(&keypad_dev)) +			goto err_lcd_unreg; +	} +	register_reboot_notifier(&panel_notifier); +	return; + +err_lcd_unreg: +	if (lcd.enabled) +		misc_deregister(&lcd_dev); +err_unreg_device: +	parport_unregister_device(pprt); +	pprt = NULL; +} + +static void panel_detach(struct parport *port) +{ +	if (port->number != parport) +		return; + +	if (!pprt) { +		pr_err("%s: port->number=%d parport=%d, nothing to unregister.\n", +		       __func__, port->number, parport); +		return; +	} +	if (scan_timer.function) +		del_timer_sync(&scan_timer); + +	if (pprt) { +		if (keypad.enabled) { +			misc_deregister(&keypad_dev); +			keypad_initialized = 0; +		} + +		if (lcd.enabled) { +			panel_lcd_print("\x0cLCD driver " PANEL_VERSION +					"\nunloaded.\x1b[Lc\x1b[Lb\x1b[L-"); +			misc_deregister(&lcd_dev); +			lcd.initialized = false; +		} + +		/* TODO: free all input signals */ +		parport_release(pprt); +		parport_unregister_device(pprt); +		pprt = NULL; +		unregister_reboot_notifier(&panel_notifier); +	} +} + +static struct parport_driver panel_driver = { +	.name = "panel", +	.match_port = panel_attach, +	.detach = panel_detach, +	.devmodel = true, +}; + +/* init function */ +static int __init panel_init_module(void) +{ +	int selected_keypad_type = NOT_SET, err; + +	/* take care of an eventual profile */ +	switch (profile) { +	case PANEL_PROFILE_CUSTOM: +		/* custom profile */ +		selected_keypad_type = DEFAULT_KEYPAD_TYPE; +		selected_lcd_type = DEFAULT_LCD_TYPE; +		break; +	case PANEL_PROFILE_OLD: +		/* 8 bits, 2*16, old keypad */ +		selected_keypad_type = KEYPAD_TYPE_OLD; +		selected_lcd_type = LCD_TYPE_OLD; + +		/* TODO: This two are a little hacky, sort it out later */ +		if (lcd_width == NOT_SET) +			lcd_width = 16; +		if (lcd_hwidth == NOT_SET) +			lcd_hwidth = 16; +		break; +	case PANEL_PROFILE_NEW: +		/* serial, 2*16, new keypad */ +		selected_keypad_type = KEYPAD_TYPE_NEW; +		selected_lcd_type = LCD_TYPE_KS0074; +		break; +	case PANEL_PROFILE_HANTRONIX: +		/* 8 bits, 2*16 hantronix-like, no keypad */ +		selected_keypad_type = KEYPAD_TYPE_NONE; +		selected_lcd_type = LCD_TYPE_HANTRONIX; +		break; +	case PANEL_PROFILE_NEXCOM: +		/* generic 8 bits, 2*16, nexcom keypad, eg. Nexcom. */ +		selected_keypad_type = KEYPAD_TYPE_NEXCOM; +		selected_lcd_type = LCD_TYPE_NEXCOM; +		break; +	case PANEL_PROFILE_LARGE: +		/* 8 bits, 2*40, old keypad */ +		selected_keypad_type = KEYPAD_TYPE_OLD; +		selected_lcd_type = LCD_TYPE_OLD; +		break; +	} + +	/* +	 * Overwrite selection with module param values (both keypad and lcd), +	 * where the deprecated params have lower prio. +	 */ +	if (keypad_enabled != NOT_SET) +		selected_keypad_type = keypad_enabled; +	if (keypad_type != NOT_SET) +		selected_keypad_type = keypad_type; + +	keypad.enabled = (selected_keypad_type > 0); + +	if (lcd_enabled != NOT_SET) +		selected_lcd_type = lcd_enabled; +	if (lcd_type != NOT_SET) +		selected_lcd_type = lcd_type; + +	lcd.enabled = (selected_lcd_type > 0); + +	if (lcd.enabled) { +		/* +		 * Init lcd struct with load-time values to preserve exact +		 * current functionality (at least for now). +		 */ +		lcd.height = lcd_height; +		lcd.width = lcd_width; +		lcd.bwidth = lcd_bwidth; +		lcd.hwidth = lcd_hwidth; +		lcd.charset = lcd_charset; +		lcd.proto = lcd_proto; +		lcd.pins.e = lcd_e_pin; +		lcd.pins.rs = lcd_rs_pin; +		lcd.pins.rw = lcd_rw_pin; +		lcd.pins.cl = lcd_cl_pin; +		lcd.pins.da = lcd_da_pin; +		lcd.pins.bl = lcd_bl_pin; + +		/* Leave it for now, just in case */ +		lcd.esc_seq.len = -1; +	} + +	switch (selected_keypad_type) { +	case KEYPAD_TYPE_OLD: +		keypad_profile = old_keypad_profile; +		break; +	case KEYPAD_TYPE_NEW: +		keypad_profile = new_keypad_profile; +		break; +	case KEYPAD_TYPE_NEXCOM: +		keypad_profile = nexcom_keypad_profile; +		break; +	default: +		keypad_profile = NULL; +		break; +	} + +	if (!lcd.enabled && !keypad.enabled) { +		/* no device enabled, let's exit */ +		pr_err("driver version " PANEL_VERSION " disabled.\n"); +		return -ENODEV; +	} + +	err = parport_register_driver(&panel_driver); +	if (err) { +		pr_err("could not register with parport. Aborting.\n"); +		return err; +	} + +	if (pprt) +		pr_info("driver version " PANEL_VERSION +			" registered on parport%d (io=0x%lx).\n", parport, +			pprt->port->base); +	else +		pr_info("driver version " PANEL_VERSION +			" not yet registered\n"); +	return 0; +} + +static void __exit panel_cleanup_module(void) +{ +	parport_unregister_driver(&panel_driver); +} + +module_init(panel_init_module); +module_exit(panel_cleanup_module); +MODULE_AUTHOR("Willy Tarreau"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + *  c-indent-level: 4 + *  tab-width: 8 + * End: + */ diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c index 9a17a9bab8d6..4810e039bbec 100644 --- a/drivers/misc/pch_phub.c +++ b/drivers/misc/pch_phub.c @@ -503,8 +503,7 @@ static ssize_t pch_phub_bin_read(struct file *filp, struct kobject *kobj,  	int err;  	ssize_t rom_size; -	struct pch_phub_reg *chip = -		dev_get_drvdata(container_of(kobj, struct device, kobj)); +	struct pch_phub_reg *chip = dev_get_drvdata(kobj_to_dev(kobj));  	ret = mutex_lock_interruptible(&pch_phub_mutex);  	if (ret) { @@ -514,8 +513,10 @@ static ssize_t pch_phub_bin_read(struct file *filp, struct kobject *kobj,  	/* Get Rom signature */  	chip->pch_phub_extrom_base_address = pci_map_rom(chip->pdev, &rom_size); -	if (!chip->pch_phub_extrom_base_address) +	if (!chip->pch_phub_extrom_base_address) { +		err = -ENODATA;  		goto exrom_map_err; +	}  	pch_phub_read_serial_rom(chip, chip->pch_opt_rom_start_address,  				(unsigned char *)&rom_signature); @@ -567,8 +568,7 @@ static ssize_t pch_phub_bin_write(struct file *filp, struct kobject *kobj,  	unsigned int addr_offset;  	int ret;  	ssize_t rom_size; -	struct pch_phub_reg *chip = -		dev_get_drvdata(container_of(kobj, struct device, kobj)); +	struct pch_phub_reg *chip = dev_get_drvdata(kobj_to_dev(kobj));  	ret = mutex_lock_interruptible(&pch_phub_mutex);  	if (ret) diff --git a/drivers/misc/ti-st/st_core.c b/drivers/misc/ti-st/st_core.c index 6e3af8b42cdd..dcdbd58672cc 100644 --- a/drivers/misc/ti-st/st_core.c +++ b/drivers/misc/ti-st/st_core.c @@ -632,7 +632,6 @@ long st_register(struct st_proto_s *new_proto)  		spin_unlock_irqrestore(&st_gdata->lock, flags);  		return err;  	} -	pr_debug("done %s(%d) ", __func__, new_proto->chnl_id);  }  EXPORT_SYMBOL_GPL(st_register); diff --git a/drivers/misc/vmw_vmci/vmci_driver.c b/drivers/misc/vmw_vmci/vmci_driver.c index b823f9a6e464..896be150e28f 100644 --- a/drivers/misc/vmw_vmci/vmci_driver.c +++ b/drivers/misc/vmw_vmci/vmci_driver.c @@ -113,5 +113,5 @@ module_exit(vmci_drv_exit);  MODULE_AUTHOR("VMware, Inc.");  MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface."); -MODULE_VERSION("1.1.3.0-k"); +MODULE_VERSION("1.1.4.0-k");  MODULE_LICENSE("GPL v2"); | 
