summaryrefslogtreecommitdiffstats
path: root/drivers/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig282
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/ad525x_dpot.c4
-rw-r--r--drivers/misc/apds990x.c8
-rw-r--r--drivers/misc/arm-charlcd.c24
-rw-r--r--drivers/misc/atmel-ssc.c1
-rw-r--r--drivers/misc/bh1770glc.c8
-rw-r--r--drivers/misc/c2port/core.c8
-rw-r--r--drivers/misc/cxl/pci.c1
-rw-r--r--drivers/misc/cxl/sysfs.c5
-rw-r--r--drivers/misc/eeprom/Kconfig6
-rw-r--r--drivers/misc/eeprom/at24.c130
-rw-r--r--drivers/misc/eeprom/at25.c148
-rw-r--r--drivers/misc/eeprom/eeprom.c2
-rw-r--r--drivers/misc/eeprom/eeprom_93xx46.c332
-rw-r--r--drivers/misc/genwqe/card_sysfs.c2
-rw-r--r--drivers/misc/ibmasm/ibmasm.h9
-rw-r--r--drivers/misc/lis3lv02d/lis3lv02d_i2c.c8
-rw-r--r--drivers/misc/lkdtm.c153
-rw-r--r--drivers/misc/mei/Kconfig6
-rw-r--r--drivers/misc/mei/Makefile1
-rw-r--r--drivers/misc/mei/amthif.c130
-rw-r--r--drivers/misc/mei/bus-fixup.c41
-rw-r--r--drivers/misc/mei/bus.c57
-rw-r--r--drivers/misc/mei/client.c189
-rw-r--r--drivers/misc/mei/client.h27
-rw-r--r--drivers/misc/mei/debugfs.c65
-rw-r--r--drivers/misc/mei/hbm.c24
-rw-r--r--drivers/misc/mei/hw-me-regs.h4
-rw-r--r--drivers/misc/mei/hw-me.c10
-rw-r--r--drivers/misc/mei/hw-txe.c10
-rw-r--r--drivers/misc/mei/hw.h32
-rw-r--r--drivers/misc/mei/init.c20
-rw-r--r--drivers/misc/mei/interrupt.c94
-rw-r--r--drivers/misc/mei/main.c106
-rw-r--r--drivers/misc/mei/mei-trace.c2
-rw-r--r--drivers/misc/mei/mei-trace.h40
-rw-r--r--drivers/misc/mei/mei_dev.h118
-rw-r--r--drivers/misc/mei/pci-me.c7
-rw-r--r--drivers/misc/mei/pci-txe.c4
-rw-r--r--drivers/misc/mei/wd.c391
-rw-r--r--drivers/misc/mic/Kconfig44
-rw-r--r--drivers/misc/mic/Makefile1
-rw-r--r--drivers/misc/mic/bus/Makefile1
-rw-r--r--drivers/misc/mic/bus/cosm_bus.h2
-rw-r--r--drivers/misc/mic/bus/vop_bus.c203
-rw-r--r--drivers/misc/mic/bus/vop_bus.h140
-rw-r--r--drivers/misc/mic/card/Makefile1
-rw-r--r--drivers/misc/mic/card/mic_device.c89
-rw-r--r--drivers/misc/mic/card/mic_device.h3
-rw-r--r--drivers/misc/mic/card/mic_virtio.c634
-rw-r--r--drivers/misc/mic/card/mic_virtio.h76
-rw-r--r--drivers/misc/mic/card/mic_x100.c1
-rw-r--r--drivers/misc/mic/cosm/cosm_main.c13
-rw-r--r--drivers/misc/mic/host/Makefile2
-rw-r--r--drivers/misc/mic/host/mic_boot.c125
-rw-r--r--drivers/misc/mic/host/mic_debugfs.c190
-rw-r--r--drivers/misc/mic/host/mic_device.h9
-rw-r--r--drivers/misc/mic/host/mic_fops.c222
-rw-r--r--drivers/misc/mic/host/mic_fops.h32
-rw-r--r--drivers/misc/mic/host/mic_main.c49
-rw-r--r--drivers/misc/mic/host/mic_virtio.c811
-rw-r--r--drivers/misc/mic/host/mic_x100.c19
-rw-r--r--drivers/misc/mic/scif/scif_dma.c41
-rw-r--r--drivers/misc/mic/scif/scif_rma.c7
-rw-r--r--drivers/misc/mic/vop/Makefile9
-rw-r--r--drivers/misc/mic/vop/vop_debugfs.c232
-rw-r--r--drivers/misc/mic/vop/vop_main.c755
-rw-r--r--drivers/misc/mic/vop/vop_main.h (renamed from drivers/misc/mic/host/mic_virtio.h)129
-rw-r--r--drivers/misc/mic/vop/vop_vringh.c1165
-rw-r--r--drivers/misc/panel.c2438
-rw-r--r--drivers/misc/pch_phub.c10
-rw-r--r--drivers/misc/ti-st/st_core.c1
-rw-r--r--drivers/misc/vmw_vmci/vmci_driver.c2
74 files changed, 6696 insertions, 3270 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 054fc10..a216b46 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 537d7f3..b2fb6dbf 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 15e88078..fe16727 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 a3e789b..dfb72ec 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 c65b5ea..b3176ee 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 e11a0bd..0516ecd 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 753d7ec..845466e 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 cc8645b..1922cb8 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 0c6c17a1..a896083 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>
diff --git a/drivers/misc/cxl/sysfs.c b/drivers/misc/cxl/sysfs.c
index 02006f71..038af5d 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 04f2e1f..cfc493c 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 5d7c090..089d694 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 f850ef5..fa36a6e 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 7342fd6..3d1d551 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 ff63f05..426fe2f 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 6ab31ef..c24c9b7 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 5bd1277..9fea49d 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 0c3bb7e..14b7d53 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 11fdadc..5f1a36b 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 d23384d..c49e1d2 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 01447ca..59e6b0a 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 cd0403f..194360a 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 020de59..e9e6ea3 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 0b05aa9..5d5996e 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 a6c87c7..bab17e4 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 04e1aa3..0d7a3a1 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 a138d8a..c6c051b 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 e7b7aad..5e305d2 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 a8a68ac..0dcb854 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 25b1997..e2fb44c 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, &reg);
+ 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, &reg);
+ 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, &reg);
+ 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 bae680c..4a6c1b8 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 4cebde8..9daf3f9 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 3edafc8..f7c8dfd 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 64b568a..1e5cb1f 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 80f9afc..52635b0 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,7 +434,7 @@ end:
*
* Return: 0 on success , <0 on error
*/
-static int mei_ioctl_client_notify_request(struct file *file, u32 request)
+static int mei_ioctl_client_notify_request(const struct file *file, u32 request)
{
struct mei_cl *cl = file->private_data;
@@ -473,7 +453,7 @@ static int mei_ioctl_client_notify_request(struct file *file, u32 request)
*
* Return: 0 on success , <0 on error
*/
-static int mei_ioctl_client_notify_get(struct file *file, u32 *notify_get)
+static int mei_ioctl_client_notify_get(const struct file *file, u32 *notify_get)
{
struct mei_cl *cl = file->private_data;
bool notify_ev;
diff --git a/drivers/misc/mei/mei-trace.c b/drivers/misc/mei/mei-trace.c
index 388efb5..e19e6ac 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 47e1bc6..7d2d5d4 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 4250555..db78e6d 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 75fc9c6..64e64da 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 71f8a74..30cc306 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 b346638..0000000
--- 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 40677df..2e4f3ba 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 e288a11..f2b1323 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 761842b..8758a7d 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 f7c57f2..8b63418 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 0000000..303da22
--- /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 0000000..fff7a86
--- /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 69d58be..6e9675e 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 d0edaf7..e749af4 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 1dbf83c..333dbed 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 f6ed57d..0000000
--- 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 d0407ba..0000000
--- 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 b2958ce..b9f0710 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 4b4b356..7005cb1 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 004d3db..f3b5023 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 7845564..8c91c99 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 1058160..0a9daba 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 461184a..52b12b2 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 8cc1d90..0000000
--- 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(&copy, 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, &copy);
- 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,
- &copy.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 dc3893d..0000000
--- 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 153894e..035be3e 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 58b107a..0000000
--- 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 8118ac4..82a973c 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 95a13c6..cd01a0e 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 8310b4d..6a451bd 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 0000000..78819c8
--- /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 0000000..ab43884
--- /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 0000000..1a2b67f3
--- /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 a80631f..ba47ec7 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 0000000..e94c7fb
--- /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(&copy, argp, sizeof(copy))) {
+ ret = -EFAULT;
+ goto _unlock_ret;
+ }
+
+ ret = vop_virtio_copy_desc(vdev, &copy);
+ if (ret < 0)
+ goto _unlock_ret;
+ if (copy_to_user(
+ &((struct mic_copy_desc __user *)argp)->out_len,
+ &copy.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 0000000..6030ac5
--- /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 9a17a9b..4810e03 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 6e3af8b..dcdbd58 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 b823f9a..896be15 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");
OpenPOWER on IntegriCloud