From d4e44f1418d71cf53d685e236a95c525089e065a Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 18 Mar 2016 12:28:49 +0200 Subject: [PATCH 001/393] ASoC: omap-mcbsp: Enable/disable sidetone block auto clock gating for omap3 OMAP3's McBSP2 and McBSP3 module have integrated sidetone block with dedicated SYSCONFIG register. The sidetone is operating from the maain McBSP module's ICLK. For normal operation the sidetone clock auto idle support needs to be disabled when it is activated. Note: This is not enough to avoid choppy sidetone because this AUTOIDLE bit is controlling only the clock auto idle from the McBSP to the sidetone block. If the McBSP_ICLK is idling, the sidetone clock is going to do the same. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/omap/mcbsp.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/omap/mcbsp.c b/sound/soc/omap/mcbsp.c index c7563e230c7d..4a16e778966b 100644 --- a/sound/soc/omap/mcbsp.c +++ b/sound/soc/omap/mcbsp.c @@ -260,6 +260,10 @@ static void omap_st_on(struct omap_mcbsp *mcbsp) if (mcbsp->pdata->enable_st_clock) mcbsp->pdata->enable_st_clock(mcbsp->id, 1); + /* Disable Sidetone clock auto-gating for normal operation */ + w = MCBSP_ST_READ(mcbsp, SYSCONFIG); + MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w & ~(ST_AUTOIDLE)); + /* Enable McBSP Sidetone */ w = MCBSP_READ(mcbsp, SSELCR); MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN); @@ -279,6 +283,10 @@ static void omap_st_off(struct omap_mcbsp *mcbsp) w = MCBSP_READ(mcbsp, SSELCR); MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN)); + /* Enable Sidetone clock auto-gating to reduce power consumption */ + w = MCBSP_ST_READ(mcbsp, SYSCONFIG); + MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE); + if (mcbsp->pdata->enable_st_clock) mcbsp->pdata->enable_st_clock(mcbsp->id, 0); } From 465011fc56717f0227d1aa7a99cce000abc614d8 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sun, 27 Mar 2016 19:06:14 -0300 Subject: [PATCH 002/393] ASoC: wm8960: Provide a menu selection text Provide a menu selection text so that users can enable, disable or mark it as module in menuconfig. Signed-off-by: Fabio Estevam Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 649e92a252ae..196101b2eab5 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -909,7 +909,7 @@ config SND_SOC_WM8955 tristate config SND_SOC_WM8960 - tristate + tristate "Wolfson Microelectronics WM8960 CODEC" config SND_SOC_WM8961 tristate From 65147846796bd443972d9055b3b4c1339e15d53a Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 28 Mar 2016 08:31:18 -0300 Subject: [PATCH 003/393] ASoC: wm8962: Disable clock if wm8962_runtime_resume() fails When regulator_bulk_enable() fails inside wm8962_runtime_resume(), we should disable the previously enabled clock. Signed-off-by: Fabio Estevam Acked-by: Charles Keepax Signed-off-by: Mark Brown --- sound/soc/codecs/wm8962.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 88223608a33f..f3f71ba0ed12 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3800,7 +3800,7 @@ static int wm8962_runtime_resume(struct device *dev) if (ret != 0) { dev_err(dev, "Failed to enable supplies: %d\n", ret); - return ret; + goto disable_clock; } regcache_cache_only(wm8962->regmap, false); @@ -3838,6 +3838,10 @@ static int wm8962_runtime_resume(struct device *dev) msleep(5); return 0; + +disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; } static int wm8962_runtime_suspend(struct device *dev) From 8bfa934e10d99b524bfe80b793e235b9188a7b58 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 28 Mar 2016 08:31:19 -0300 Subject: [PATCH 004/393] ASoC: wm8962: Fit error message into a single line The error message fits well into a single line. Signed-off-by: Fabio Estevam Acked-by: Charles Keepax Signed-off-by: Mark Brown --- sound/soc/codecs/wm8962.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index f3f71ba0ed12..93f75dcee388 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3798,8 +3798,7 @@ static int wm8962_runtime_resume(struct device *dev) ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies); if (ret != 0) { - dev_err(dev, - "Failed to enable supplies: %d\n", ret); + dev_err(dev, "Failed to enable supplies: %d\n", ret); goto disable_clock; } From 937e92dc50231bb41294262abf56d2bdddc4d38c Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sun, 27 Mar 2016 18:36:13 -0300 Subject: [PATCH 005/393] ASoC: wm8962: Adjust clk definitions so that simple card can work When trying to use simple card with wm8962 the following probe error happens: wm8962 0-001a: simple-card: set_sysclk error asoc-simple-card sound: ASoC: failed to init 202c000.ssi-wm8962: -22 asoc-simple-card sound: ASoC: failed to instantiate card -22 asoc-simple-card: probe of sound failed with error -22 In simple-card.c the snd_soc_dai_set_sysclk() function is called with clk_id as 0, which is an invalid clock for wm8962. Adjust the clocks source definitions in wm8962.h so that the simple card driver can work successfully. Signed-off-by: Fabio Estevam Acked-by: Charles Keepax Signed-off-by: Mark Brown --- sound/soc/codecs/wm8962.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/wm8962.h b/sound/soc/codecs/wm8962.h index 910aafd09d21..e63a318a3015 100644 --- a/sound/soc/codecs/wm8962.h +++ b/sound/soc/codecs/wm8962.h @@ -16,9 +16,9 @@ #include #include -#define WM8962_SYSCLK_MCLK 1 -#define WM8962_SYSCLK_FLL 2 -#define WM8962_SYSCLK_PLL3 3 +#define WM8962_SYSCLK_MCLK 0 +#define WM8962_SYSCLK_FLL 1 +#define WM8962_SYSCLK_PLL3 2 #define WM8962_FLL 1 From 0400485076e8bb167d5f4b3eb5f6d05e4b4361b7 Mon Sep 17 00:00:00 2001 From: Petr Kulhavy Date: Tue, 29 Mar 2016 09:39:36 +0200 Subject: [PATCH 006/393] ASoC: tas571x: implemented digital mute The driver did not have a mute function. The amplifier was brought out of shutdown mode (hard-mute) once for ever in probe(), which was causing clicks and pops when altering the I2C register configuration later. This adds the digital_mute() function. The amplifier unmute in probe() was removed. Signed-off-by: Petr Kulhavy Signed-off-by: Mark Brown --- sound/soc/codecs/tas571x.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index 39307ad41a34..d003c6ce0794 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -167,6 +167,23 @@ static int tas571x_hw_params(struct snd_pcm_substream *substream, TAS571X_SDI_FMT_MASK, val); } +static int tas571x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 sysctl2; + int ret; + + sysctl2 = mute ? TAS571X_SYS_CTRL_2_SDN_MASK : 0; + + ret = snd_soc_update_bits(codec, + TAS571X_SYS_CTRL_2_REG, + TAS571X_SYS_CTRL_2_SDN_MASK, + sysctl2); + usleep_range(1000, 2000); + + return ret; +} + static int tas571x_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -214,6 +231,7 @@ static int tas571x_set_bias_level(struct snd_soc_codec *codec, static const struct snd_soc_dai_ops tas571x_dai_ops = { .set_fmt = tas571x_set_dai_fmt, .hw_params = tas571x_hw_params, + .digital_mute = tas571x_mute, }; static const char *const tas5711_supply_names[] = { @@ -445,10 +463,6 @@ static int tas571x_i2c_probe(struct i2c_client *client, if (ret) return ret; - ret = regmap_update_bits(priv->regmap, TAS571X_SYS_CTRL_2_REG, - TAS571X_SYS_CTRL_2_SDN_MASK, 0); - if (ret) - return ret; memcpy(&priv->codec_driver, &tas571x_codec, sizeof(priv->codec_driver)); priv->codec_driver.controls = priv->chip->controls; From e42839b012eb8179ed2e3571bfcd133fba5df154 Mon Sep 17 00:00:00 2001 From: Mengdong Lin Date: Thu, 24 Mar 2016 10:53:14 +0800 Subject: [PATCH 007/393] ASoC: topology: ABI - Define types for vendor tuples Tuples, a pair of token and value, can be used to define vendor specific data, for controls and widgets. This can avoid importing binary data blob from other files. Vendor specific tuple arrays will be embeded in the private data buffer of a control or widget object. To be backward compatible, union is used to define the tuple arrays in the existing private data ABI object 'struct snd_soc_tplg_private'. Vendors need to make sure the token values defined by the topology conf file match those defined by their driver. Now supported tuple types are uuid, string, bool, byte, short and word. Signed-off-by: Mengdong Lin Signed-off-by: Mark Brown --- include/uapi/sound/asoc.h | 42 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/include/uapi/sound/asoc.h b/include/uapi/sound/asoc.h index c4cc1e40b35c..db86b447b515 100644 --- a/include/uapi/sound/asoc.h +++ b/include/uapi/sound/asoc.h @@ -116,6 +116,14 @@ #define SND_SOC_TPLG_STREAM_PLAYBACK 0 #define SND_SOC_TPLG_STREAM_CAPTURE 1 +/* vendor tuple types */ +#define SND_SOC_TPLG_TUPLE_TYPE_UUID 0 +#define SND_SOC_TPLG_TUPLE_TYPE_STRING 1 +#define SND_SOC_TPLG_TUPLE_TYPE_BOOL 2 +#define SND_SOC_TPLG_TUPLE_TYPE_BYTE 3 +#define SND_SOC_TPLG_TUPLE_TYPE_WORD 4 +#define SND_SOC_TPLG_TUPLE_TYPE_SHORT 5 + /* * Block Header. * This header precedes all object and object arrays below. @@ -132,6 +140,35 @@ struct snd_soc_tplg_hdr { __le32 count; /* number of elements in block */ } __attribute__((packed)); +/* vendor tuple for uuid */ +struct snd_soc_tplg_vendor_uuid_elem { + __le32 token; + char uuid[16]; +} __attribute__((packed)); + +/* vendor tuple for a bool/byte/short/word value */ +struct snd_soc_tplg_vendor_value_elem { + __le32 token; + __le32 value; +} __attribute__((packed)); + +/* vendor tuple for string */ +struct snd_soc_tplg_vendor_string_elem { + __le32 token; + char string[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; +} __attribute__((packed)); + +struct snd_soc_tplg_vendor_array { + __le32 size; /* size in bytes of the array, including all elements */ + __le32 type; /* SND_SOC_TPLG_TUPLE_TYPE_ */ + __le32 num_elems; /* number of elements in array */ + union { + struct snd_soc_tplg_vendor_uuid_elem uuid[0]; + struct snd_soc_tplg_vendor_value_elem value[0]; + struct snd_soc_tplg_vendor_string_elem string[0]; + }; +} __attribute__((packed)); + /* * Private data. * All topology objects may have private data that can be used by the driver or @@ -139,7 +176,10 @@ struct snd_soc_tplg_hdr { */ struct snd_soc_tplg_private { __le32 size; /* in bytes of private data */ - char data[0]; + union { + char data[0]; + struct snd_soc_tplg_vendor_array array[0]; + }; } __attribute__((packed)); /* From 2dfadff69e8b1da8f8661e9edb131b208cc389b7 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Wed, 30 Mar 2016 18:25:07 +0800 Subject: [PATCH 008/393] ASoC: rt5677: Avoid duplicate the same test in each switch case Signed-off-by: Axel Lin Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 33e290b703df..b3f1db5bae4a 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -1241,60 +1241,46 @@ static int rt5677_dmic_use_asrc(struct snd_soc_dapm_widget *source, regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO1_CLK_SEL_MASK) >> RT5677_AD_STO1_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 10: regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO2_CLK_SEL_MASK) >> RT5677_AD_STO2_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 9: regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO3_CLK_SEL_MASK) >> RT5677_AD_STO3_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 8: regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_STO4_CLK_SEL_MASK) >> RT5677_AD_STO4_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 7: regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_MONOL_CLK_SEL_MASK) >> RT5677_AD_MONOL_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; case 6: regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting); asrc_setting = (asrc_setting & RT5677_AD_MONOR_CLK_SEL_MASK) >> RT5677_AD_MONOR_CLK_SEL_SFT; - if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && - asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) - return 1; break; default: - break; + return 0; } + if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && + asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) + return 1; + return 0; } From 3fcdfc9dad07c2a6ea19dfb98a2151cd86d06c6a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 30 Mar 2016 11:02:49 -0700 Subject: [PATCH 009/393] ASoC: wm8960: Depends on I2C Now that this is directly user selectable it needs to care about its dependencies. Reported-by: kbuild test robot Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 196101b2eab5..3ebf29bc87d3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -910,6 +910,7 @@ config SND_SOC_WM8955 config SND_SOC_WM8960 tristate "Wolfson Microelectronics WM8960 CODEC" + depends on I2C config SND_SOC_WM8961 tristate From 630e413dc24da0c2373fd7592aeb0e08cea71cd1 Mon Sep 17 00:00:00 2001 From: Petr Kulhavy Date: Tue, 29 Mar 2016 09:39:35 +0200 Subject: [PATCH 010/393] ASoC: tas571x: chip type detection via I2C name The chip selection was relying only on DT. It was not possible to use the driver without DT. This adds the chip type detection from the I2C name, which allows to use the driver from the platform driver without DT. Signed-off-by: Petr Kulhavy Reviewed-by: Kevin Cernekee Signed-off-by: Mark Brown --- sound/soc/codecs/tas571x.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index d003c6ce0794..aafee9bbe01a 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -404,11 +404,10 @@ static int tas571x_i2c_probe(struct i2c_client *client, i2c_set_clientdata(client, priv); of_id = of_match_device(tas571x_of_match, dev); - if (!of_id) { - dev_err(dev, "Unknown device type\n"); - return -EINVAL; - } - priv->chip = of_id->data; + if (of_id) + priv->chip = of_id->data; + else + priv->chip = (void *) id->driver_data; priv->mclk = devm_clk_get(dev, "mclk"); if (IS_ERR(priv->mclk) && PTR_ERR(priv->mclk) != -ENOENT) { @@ -505,9 +504,9 @@ static const struct of_device_id tas571x_of_match[] = { MODULE_DEVICE_TABLE(of, tas571x_of_match); static const struct i2c_device_id tas571x_i2c_id[] = { - { "tas5711", 0 }, - { "tas5717", 0 }, - { "tas5719", 0 }, + { "tas5711", (kernel_ulong_t) &tas5711_chip }, + { "tas5717", (kernel_ulong_t) &tas5717_chip }, + { "tas5719", (kernel_ulong_t) &tas5717_chip }, { } }; MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id); From a593ed09040fa611f37953afe455e64c7653160d Mon Sep 17 00:00:00 2001 From: Petr Kulhavy Date: Thu, 31 Mar 2016 18:41:25 +0200 Subject: [PATCH 011/393] ASoC: tas571x: added missing register literals The list of TAS571x registers was incomplete. Added the missing register definitions up to the register 0x25. Added volatile and read-only register tables into tas5711_regmap_config and tas5717_regmap_config. The chip has 256 registers in total. But from address 0x29 on (0x26 to 0x28 are reserved) the register width varies between 20, 12 and 8 bytes, which the register map cannot represent. Signed-off-by: Petr Kulhavy Signed-off-by: Mark Brown --- sound/soc/codecs/tas571x.c | 28 ++++++++++++++++++++++++++++ sound/soc/codecs/tas571x.h | 22 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index aafee9bbe01a..ef6c8d9b251a 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -57,6 +57,10 @@ static int tas571x_register_size(struct tas571x_private *priv, unsigned int reg) case TAS571X_CH1_VOL_REG: case TAS571X_CH2_VOL_REG: return priv->chip->vol_reg_size; + case TAS571X_INPUT_MUX_REG: + case TAS571X_CH4_SRC_SELECT_REG: + case TAS571X_PWM_MUX_REG: + return 4; default: return 1; } @@ -259,6 +263,26 @@ static const struct snd_kcontrol_new tas5711_controls[] = { 1, 1), }; +static const struct regmap_range tas571x_readonly_regs_range[] = { + regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_DEV_ID_REG), +}; + +static const struct regmap_range tas571x_volatile_regs_range[] = { + regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_ERR_STATUS_REG), + regmap_reg_range(TAS571X_OSC_TRIM_REG, TAS571X_OSC_TRIM_REG), +}; + +static const struct regmap_access_table tas571x_write_regs = { + .no_ranges = tas571x_readonly_regs_range, + .n_no_ranges = ARRAY_SIZE(tas571x_readonly_regs_range), +}; + +static const struct regmap_access_table tas571x_volatile_regs = { + .yes_ranges = tas571x_volatile_regs_range, + .n_yes_ranges = ARRAY_SIZE(tas571x_volatile_regs_range), + +}; + static const struct reg_default tas5711_reg_defaults[] = { { 0x04, 0x05 }, { 0x05, 0x40 }, @@ -278,6 +302,8 @@ static const struct regmap_config tas5711_regmap_config = { .reg_defaults = tas5711_reg_defaults, .num_reg_defaults = ARRAY_SIZE(tas5711_reg_defaults), .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, }; static const struct tas571x_chip tas5711_chip = { @@ -332,6 +358,8 @@ static const struct regmap_config tas5717_regmap_config = { .reg_defaults = tas5717_reg_defaults, .num_reg_defaults = ARRAY_SIZE(tas5717_reg_defaults), .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, }; /* This entry is reused for tas5719 as the software interface is identical. */ diff --git a/sound/soc/codecs/tas571x.h b/sound/soc/codecs/tas571x.h index 0aee471232cd..cf800c364f0f 100644 --- a/sound/soc/codecs/tas571x.h +++ b/sound/soc/codecs/tas571x.h @@ -13,6 +13,10 @@ #define _TAS571X_H /* device registers */ +#define TAS571X_CLK_CTRL_REG 0x00 +#define TAS571X_DEV_ID_REG 0x01 +#define TAS571X_ERR_STATUS_REG 0x02 +#define TAS571X_SYS_CTRL_1_REG 0x03 #define TAS571X_SDI_REG 0x04 #define TAS571X_SDI_FMT_MASK 0x0f @@ -27,7 +31,25 @@ #define TAS571X_MVOL_REG 0x07 #define TAS571X_CH1_VOL_REG 0x08 #define TAS571X_CH2_VOL_REG 0x09 +#define TAS571X_CH3_VOL_REG 0x0a +#define TAS571X_VOL_CFG_REG 0x0e +#define TAS571X_MODULATION_LIMIT_REG 0x10 +#define TAS571X_IC_DELAY_CH1_REG 0x11 +#define TAS571X_IC_DELAY_CH2_REG 0x12 +#define TAS571X_IC_DELAY_CH3_REG 0x13 +#define TAS571X_IC_DELAY_CH4_REG 0x14 +#define TAS571X_PWM_CH_SDN_GROUP_REG 0x19 /* N/A on TAS5717, TAS5719 */ +#define TAS571X_PWM_CH1_SDN_MASK (1<<0) +#define TAS571X_PWM_CH2_SDN_SHIFT (1<<1) +#define TAS571X_PWM_CH3_SDN_SHIFT (1<<2) +#define TAS571X_PWM_CH4_SDN_SHIFT (1<<3) + +#define TAS571X_START_STOP_PERIOD_REG 0x1a #define TAS571X_OSC_TRIM_REG 0x1b +#define TAS571X_BKND_ERR_REG 0x1c +#define TAS571X_INPUT_MUX_REG 0x20 +#define TAS571X_CH4_SRC_SELECT_REG 0x21 +#define TAS571X_PWM_MUX_REG 0x25 #endif /* _TAS571X_H */ From 23a282c4f088efb337957ffa21c677d30eda0784 Mon Sep 17 00:00:00 2001 From: Petr Kulhavy Date: Thu, 31 Mar 2016 18:41:26 +0200 Subject: [PATCH 012/393] ASoC: tas571x: added support for TAS5721 This adds support for TAS5721. Signed-off-by: Petr Kulhavy Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 2 +- sound/soc/codecs/tas571x.c | 76 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 649e92a252ae..c011f076d58b 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -737,7 +737,7 @@ config SND_SOC_TAS5086 depends on I2C config SND_SOC_TAS571X - tristate "Texas Instruments TAS5711/TAS5717/TAS5719 power amplifiers" + tristate "Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 power amplifiers" depends on I2C config SND_SOC_TFA9879 diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index ef6c8d9b251a..b8d19b77bde9 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -4,6 +4,9 @@ * Copyright (C) 2015 Google, Inc. * Copyright (c) 2013 Daniel Mack * + * TAS5721 support: + * Copyright (C) 2016 Petr Kulhavy, Barix AG + * * 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 @@ -372,6 +375,77 @@ static const struct tas571x_chip tas5717_chip = { .vol_reg_size = 2, }; +static const char *const tas5721_supply_names[] = { + "AVDD", + "DVDD", + "DRVDD", + "PVDD", +}; + +static const struct snd_kcontrol_new tas5721_controls[] = { + SOC_SINGLE_TLV("Master Volume", + TAS571X_MVOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE_R_TLV("Speaker Volume", + TAS571X_CH1_VOL_REG, + TAS571X_CH2_VOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE("Speaker Switch", + TAS571X_SOFT_MUTE_REG, + TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, + 1, 1), +}; + +static const struct reg_default tas5721_reg_defaults[] = { + {TAS571X_CLK_CTRL_REG, 0x6c}, + {TAS571X_DEV_ID_REG, 0x00}, + {TAS571X_ERR_STATUS_REG, 0x00}, + {TAS571X_SYS_CTRL_1_REG, 0xa0}, + {TAS571X_SDI_REG, 0x05}, + {TAS571X_SYS_CTRL_2_REG, 0x40}, + {TAS571X_SOFT_MUTE_REG, 0x00}, + {TAS571X_MVOL_REG, 0xff}, + {TAS571X_CH1_VOL_REG, 0x30}, + {TAS571X_CH2_VOL_REG, 0x30}, + {TAS571X_CH3_VOL_REG, 0x30}, + {TAS571X_VOL_CFG_REG, 0x91}, + {TAS571X_MODULATION_LIMIT_REG, 0x02}, + {TAS571X_IC_DELAY_CH1_REG, 0xac}, + {TAS571X_IC_DELAY_CH2_REG, 0x54}, + {TAS571X_IC_DELAY_CH3_REG, 0xac}, + {TAS571X_IC_DELAY_CH4_REG, 0x54}, + {TAS571X_PWM_CH_SDN_GROUP_REG, 0x30}, + {TAS571X_START_STOP_PERIOD_REG, 0x0f}, + {TAS571X_OSC_TRIM_REG, 0x82}, + {TAS571X_BKND_ERR_REG, 0x02}, + {TAS571X_INPUT_MUX_REG, 0x17772}, + {TAS571X_CH4_SRC_SELECT_REG, 0x4303}, + {TAS571X_PWM_MUX_REG, 0x1021345}, +}; + +static const struct regmap_config tas5721_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = 0xff, + .reg_read = tas571x_reg_read, + .reg_write = tas571x_reg_write, + .reg_defaults = tas5721_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5721_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, +}; + + +static const struct tas571x_chip tas5721_chip = { + .supply_names = tas5721_supply_names, + .num_supply_names = ARRAY_SIZE(tas5721_supply_names), + .controls = tas5711_controls, + .num_controls = ARRAY_SIZE(tas5711_controls), + .regmap_config = &tas5721_regmap_config, + .vol_reg_size = 1, +}; + static const struct snd_soc_dapm_widget tas571x_dapm_widgets[] = { SND_SOC_DAPM_DAC("DACL", NULL, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DACR", NULL, SND_SOC_NOPM, 0, 0), @@ -527,6 +601,7 @@ static const struct of_device_id tas571x_of_match[] = { { .compatible = "ti,tas5711", .data = &tas5711_chip, }, { .compatible = "ti,tas5717", .data = &tas5717_chip, }, { .compatible = "ti,tas5719", .data = &tas5717_chip, }, + { .compatible = "ti,tas5721", .data = &tas5721_chip, }, { } }; MODULE_DEVICE_TABLE(of, tas571x_of_match); @@ -535,6 +610,7 @@ static const struct i2c_device_id tas571x_i2c_id[] = { { "tas5711", (kernel_ulong_t) &tas5711_chip }, { "tas5717", (kernel_ulong_t) &tas5717_chip }, { "tas5719", (kernel_ulong_t) &tas5717_chip }, + { "tas5721", (kernel_ulong_t) &tas5721_chip }, { } }; MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id); From a42121b7a1d80a0eaac9ed1442760bf4e65f5352 Mon Sep 17 00:00:00 2001 From: Petr Kulhavy Date: Thu, 31 Mar 2016 18:41:27 +0200 Subject: [PATCH 013/393] ASoC: tas571x: new chip added into TAS571x binding This adds the TAS5721 into the TAS571x binding. Signed-off-by: Petr Kulhavy Reviewed-by: Kevin Cernekee Acked-by: Rob Herring Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/tas571x.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/tas571x.txt b/Documentation/devicetree/bindings/sound/tas571x.txt index 0ac31d8d5ac4..b4959f10b74b 100644 --- a/Documentation/devicetree/bindings/sound/tas571x.txt +++ b/Documentation/devicetree/bindings/sound/tas571x.txt @@ -1,4 +1,4 @@ -Texas Instruments TAS5711/TAS5717/TAS5719 stereo power amplifiers +Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 stereo power amplifiers The codec is controlled through an I2C interface. It also has two other signals that can be wired up to GPIOs: reset (strongly recommended), and @@ -6,7 +6,11 @@ powerdown (optional). Required properties: -- compatible: "ti,tas5711", "ti,tas5717", or "ti,tas5719" +- compatible: should be one of the following: + - "ti,tas5711", + - "ti,tas5717", + - "ti,tas5719", + - "ti,tas5721" - reg: The I2C address of the device - #sound-dai-cells: must be equal to 0 @@ -25,6 +29,8 @@ Optional properties: - PVDD_B-supply: regulator phandle for the PVDD_B supply (5711) - PVDD_C-supply: regulator phandle for the PVDD_C supply (5711) - PVDD_D-supply: regulator phandle for the PVDD_D supply (5711) +- DRVDD-supply: regulator phandle for the DRVDD supply (5721) +- PVDD-supply: regulator phandle for the PVDD supply (5721) Example: From ea4d25d5a3709cba0d3df2195edffc400170394f Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 31 Mar 2016 19:23:14 +0100 Subject: [PATCH 014/393] ASoC: qcom: Fix uninitialized symbol warning. This patch fixes following static checker warning, by initializing the ret to -EINVAL, as one of the code path in lpass_platform_pcm_new() uses this variable uninitialized. sound/soc/qcom/lpass-platform.c:555 lpass_platform_pcm_new() error: uninitialized symbol 'ret'. Reported-by: Dan Carpenter Signed-off-by: Srinivas Kandagatla Signed-off-by: Mark Brown --- sound/soc/qcom/lpass-platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index 6e8665430bd5..f5cae01d18bd 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -474,7 +474,7 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) struct lpass_data *drvdata = snd_soc_platform_get_drvdata(soc_runtime->platform); struct lpass_variant *v = drvdata->variant; - int ret; + int ret = -EINVAL; struct lpass_pcm_data *data; size_t size = lpass_platform_pcm_hardware.buffer_bytes_max; From cef794f76485f4a4e6affd851ae9f9d0eb4287a5 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 31 Mar 2016 19:23:15 +0100 Subject: [PATCH 015/393] ASoC: qcom: remove IS_ERR_VALUE usage on int. IS_ERR_VALUE should be used only with unsigned long type, signed types should use comparison 'ret < 0' This patch removes such usages. Reported-by: Dan Carpenter Signed-off-by: Srinivas Kandagatla Signed-off-by: Mark Brown --- sound/soc/qcom/lpass-platform.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index f5cae01d18bd..db000c6987a1 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -491,7 +491,7 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) data->rdma_ch = v->alloc_dma_channel(drvdata, SNDRV_PCM_STREAM_PLAYBACK); - if (IS_ERR_VALUE(data->rdma_ch)) + if (data->rdma_ch < 0) return data->rdma_ch; drvdata->substream[data->rdma_ch] = psubstream; @@ -518,8 +518,10 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) data->wrdma_ch = v->alloc_dma_channel(drvdata, SNDRV_PCM_STREAM_CAPTURE); - if (IS_ERR_VALUE(data->wrdma_ch)) + if (data->wrdma_ch < 0) { + ret = data->wrdma_ch; goto capture_alloc_err; + } drvdata->substream[data->wrdma_ch] = csubstream; From 5ba10dd4a1727714faaac9a48f6aa69d8ee33248 Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 31 Mar 2016 18:00:55 +0200 Subject: [PATCH 016/393] ASoC: sti: correct typo errors Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- .../bindings/sound/st,sti-asoc-card.txt | 4 ++-- sound/soc/sti/uniperif.h | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt b/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt index 028fa1c82f50..f546dd914812 100644 --- a/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt +++ b/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt @@ -65,7 +65,7 @@ Example: reg = <0x8D82000 0x158>; interrupts = ; dmas = <&fdma0 4 0 1>; - dai-name = "Uni Player #1 (DAC)"; + dai-name = "Uni Player #2 (DAC)"; dma-names = "tx"; uniperiph-id = <2>; version = <5>; @@ -82,7 +82,7 @@ Example: interrupts = ; dmas = <&fdma0 7 0 1>; dma-names = "tx"; - dai-name = "Uni Player #1 (PIO)"; + dai-name = "Uni Player #3 (SPDIF)"; uniperiph-id = <3>; version = <5>; mode = "SPDIF"; diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index f0fd5a9944e9..1f82faafb869 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -25,7 +25,7 @@ writel_relaxed((((value) & mask) << shift), ip->base + offset) /* - * AUD_UNIPERIF_SOFT_RST reg + * UNIPERIF_SOFT_RST reg */ #define UNIPERIF_SOFT_RST_OFFSET(ip) 0x0000 @@ -50,7 +50,7 @@ UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip)) /* - * AUD_UNIPERIF_FIFO_DATA reg + * UNIPERIF_FIFO_DATA reg */ #define UNIPERIF_FIFO_DATA_OFFSET(ip) 0x0004 @@ -58,7 +58,7 @@ writel_relaxed(value, ip->base + UNIPERIF_FIFO_DATA_OFFSET(ip)) /* - * AUD_UNIPERIF_CHANNEL_STA_REGN reg + * UNIPERIF_CHANNEL_STA_REGN reg */ #define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) @@ -105,7 +105,7 @@ writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip)) /* - * AUD_UNIPERIF_ITS reg + * UNIPERIF_ITS reg */ #define UNIPERIF_ITS_OFFSET(ip) 0x000C @@ -143,7 +143,7 @@ 0 : (BIT(UNIPERIF_ITS_UNDERFLOW_REC_FAILED_SHIFT(ip)))) /* - * AUD_UNIPERIF_ITS_BCLR reg + * UNIPERIF_ITS_BCLR reg */ /* FIFO_ERROR */ @@ -160,7 +160,7 @@ writel_relaxed(value, ip->base + UNIPERIF_ITS_BCLR_OFFSET(ip)) /* - * AUD_UNIPERIF_ITM reg + * UNIPERIF_ITM reg */ #define UNIPERIF_ITM_OFFSET(ip) 0x0018 @@ -188,7 +188,7 @@ 0 : (BIT(UNIPERIF_ITM_UNDERFLOW_REC_FAILED_SHIFT(ip)))) /* - * AUD_UNIPERIF_ITM_BCLR reg + * UNIPERIF_ITM_BCLR reg */ #define UNIPERIF_ITM_BCLR_OFFSET(ip) 0x001c @@ -213,7 +213,7 @@ UNIPERIF_ITM_BCLR_DMA_ERROR_MASK(ip)) /* - * AUD_UNIPERIF_ITM_BSET reg + * UNIPERIF_ITM_BSET reg */ #define UNIPERIF_ITM_BSET_OFFSET(ip) 0x0020 @@ -767,7 +767,7 @@ SET_UNIPERIF_REG(ip, \ UNIPERIF_CTRL_OFFSET(ip), \ UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ - CORAUD_UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1) + UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1) /* UNDERFLOW_REC_WINDOW */ #define UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip) 20 @@ -1046,7 +1046,7 @@ UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip), value) /* - * AUD_UNIPERIF_CHANNEL_STA_REGN reg + * UNIPERIF_CHANNEL_STA_REGN reg */ #define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) @@ -1057,7 +1057,7 @@ UNIPERIF_CHANNEL_STA_REGN(ip, n)) /* - * AUD_UNIPERIF_USER_VALIDITY reg + * UNIPERIF_USER_VALIDITY reg */ #define UNIPERIF_USER_VALIDITY_OFFSET(ip) 0x0090 From 38535e8e6962405b7d8a365f2f367f62bca98565 Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 7 Apr 2016 11:25:30 +0200 Subject: [PATCH 017/393] ASoC: sti: macro for uniperif tdm regs access Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/uniperif.h | 112 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index 1f82faafb869..400788bc9190 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1100,6 +1100,118 @@ UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip), \ UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip), value) +/* + * UNIPERIF_TDM_ENABLE + */ +#define UNIPERIF_TDM_ENABLE_OFFSET(ip) 0x0118 +#define GET_UNIPERIF_TDM_ENABLE(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip)) +#define SET_UNIPERIF_TDM_ENABLE(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip)) + +/* TDM_ENABLE */ +#define UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip) 0x0 +#define UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip) 0x1 +#define GET_UNIPERIF_TDM_ENABLE_EN_TDM(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip)) +#define SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 1) +#define SET_UNIPERIF_TDM_ENABLE_TDM_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 0) + +/* + * UNIPERIF_TDM_FS_REF_FREQ + */ +#define UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip) 0x011c +#define GET_UNIPERIF_TDM_FS_REF_FREQ(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ(ip, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip)) + +/* REF_FREQ */ +#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip) 0x0 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) 0 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) 1 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) 2 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) 3 +#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip) 0x3 +#define GET_UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip)) + +/* + * UNIPERIF_TDM_FS_REF_DIV + */ +#define UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip) 0x0120 +#define GET_UNIPERIF_TDM_FS_REF_DIV(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip)) +#define SET_UNIPERIF_TDM_FS_REF_DIV(ip, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip)) + +/* NUM_TIMESLOT */ +#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip) 0x0 +#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip) 0xff +#define GET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip)) +#define SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip), value) + +/* + * UNIPERIF_TDM_WORD_POS_X_Y + * 32 bits of UNIPERIF_TDM_WORD_POS_X_Y register shall be set in 1 shot + */ +#define UNIPERIF_TDM_WORD_POS_1_2_OFFSET(ip) 0x013c +#define UNIPERIF_TDM_WORD_POS_3_4_OFFSET(ip) 0x0140 +#define UNIPERIF_TDM_WORD_POS_5_6_OFFSET(ip) 0x0144 +#define UNIPERIF_TDM_WORD_POS_7_8_OFFSET(ip) 0x0148 +#define GET_UNIPERIF_TDM_WORD_POS(ip, words) \ + readl_relaxed(ip->base + UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip)) +#define SET_UNIPERIF_TDM_WORD_POS(ip, words, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip)) /* * uniperipheral IP capabilities */ From 5295a0dc31d5261ff64406ece25e8d9e91530d2e Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 7 Apr 2016 11:25:31 +0200 Subject: [PATCH 018/393] ASoC: sti: rename unip player type into common player & reader type Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/uniperif.h | 19 ++++++++++++++----- sound/soc/sti/uniperif_player.c | 31 +++++++++++-------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index 400788bc9190..75116ab3cbc0 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1219,6 +1219,15 @@ #define UNIPERIF_FIFO_SIZE 70 /* FIFO is 70 cells deep */ #define UNIPERIF_FIFO_FRAMES 4 /* FDMA trigger limit in frames */ +#define UNIPERIF_TYPE_IS_HDMI(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_HDMI) +#define UNIPERIF_TYPE_IS_PCM(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_PCM) +#define UNIPERIF_TYPE_IS_SPDIF(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_SPDIF) +#define UNIPERIF_TYPE_IS_IEC958(p) \ + (UNIPERIF_TYPE_IS_HDMI(p) || \ + UNIPERIF_TYPE_IS_SPDIF(p)) /* * Uniperipheral IP revisions */ @@ -1237,10 +1246,10 @@ enum uniperif_version { }; enum uniperif_type { - SND_ST_UNIPERIF_PLAYER_TYPE_NONE, - SND_ST_UNIPERIF_PLAYER_TYPE_HDMI, - SND_ST_UNIPERIF_PLAYER_TYPE_PCM, - SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF + SND_ST_UNIPERIF_TYPE_NONE, + SND_ST_UNIPERIF_TYPE_HDMI, + SND_ST_UNIPERIF_TYPE_PCM, + SND_ST_UNIPERIF_TYPE_SPDIF }; enum uniperif_state { @@ -1259,7 +1268,7 @@ enum uniperif_iec958_encoding_mode { struct uniperif_info { int id; /* instance value of the uniperipheral IP */ - enum uniperif_type player_type; + enum uniperif_type type; int underflow_enabled; /* Underflow recovery mode */ }; diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index 7aca6b92f718..ab4310a1615e 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -26,15 +26,6 @@ /* * Driver specific types. */ -#define UNIPERIF_PLAYER_TYPE_IS_HDMI(p) \ - ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_HDMI) -#define UNIPERIF_PLAYER_TYPE_IS_PCM(p) \ - ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_PCM) -#define UNIPERIF_PLAYER_TYPE_IS_SPDIF(p) \ - ((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF) -#define UNIPERIF_PLAYER_TYPE_IS_IEC958(p) \ - (UNIPERIF_PLAYER_TYPE_IS_HDMI(p) || \ - UNIPERIF_PLAYER_TYPE_IS_SPDIF(p)) #define UNIPERIF_PLAYER_CLK_ADJ_MIN -999999 #define UNIPERIF_PLAYER_CLK_ADJ_MAX 1000000 @@ -738,14 +729,14 @@ static int uni_player_prepare(struct snd_pcm_substream *substream, SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(player, trigger_limit); /* Uniperipheral setup depends on player type */ - switch (player->info->player_type) { - case SND_ST_UNIPERIF_PLAYER_TYPE_HDMI: + switch (player->info->type) { + case SND_ST_UNIPERIF_TYPE_HDMI: ret = uni_player_prepare_iec958(player, runtime); break; - case SND_ST_UNIPERIF_PLAYER_TYPE_PCM: + case SND_ST_UNIPERIF_TYPE_PCM: ret = uni_player_prepare_pcm(player, runtime); break; - case SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF: + case SND_ST_UNIPERIF_TYPE_SPDIF: ret = uni_player_prepare_iec958(player, runtime); break; default: @@ -852,8 +843,8 @@ static int uni_player_start(struct uniperif *player) * will not take affect and hang the player. */ if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) - if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player)) - SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); + if (UNIPERIF_TYPE_IS_IEC958(player)) + SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); /* Force channel status update (no update if clk disable) */ if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) @@ -1012,13 +1003,13 @@ static int uni_player_parse_dt(struct platform_device *pdev, } if (strcasecmp(mode, "hdmi") == 0) - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_HDMI; + info->type = SND_ST_UNIPERIF_TYPE_HDMI; else if (strcasecmp(mode, "pcm") == 0) - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_PCM; + info->type = SND_ST_UNIPERIF_TYPE_PCM; else if (strcasecmp(mode, "spdif") == 0) - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF; + info->type = SND_ST_UNIPERIF_TYPE_SPDIF; else - info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_NONE; + info->type = SND_ST_UNIPERIF_TYPE_NONE; /* Save the info structure */ player->info = info; @@ -1087,7 +1078,7 @@ int uni_player_init(struct platform_device *pdev, SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player); - if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player)) { + if (UNIPERIF_TYPE_IS_IEC958(player)) { /* Set default iec958 status bits */ /* Consumer, PCM, copyright, 2ch, mode 0 */ From 9a00a3e9fea1ea5affa7bea971299d3a5832daad Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 7 Apr 2016 11:25:32 +0200 Subject: [PATCH 019/393] ASoC: sti: define tdm type & default tdm hw config Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/uniperif.h | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index 75116ab3cbc0..750eb5a07e6c 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1228,6 +1228,9 @@ #define UNIPERIF_TYPE_IS_IEC958(p) \ (UNIPERIF_TYPE_IS_HDMI(p) || \ UNIPERIF_TYPE_IS_SPDIF(p)) +#define UNIPERIF_TYPE_IS_TDM(p) \ + ((p)->info->type == SND_ST_UNIPERIF_TYPE_TDM) + /* * Uniperipheral IP revisions */ @@ -1249,7 +1252,8 @@ enum uniperif_type { SND_ST_UNIPERIF_TYPE_NONE, SND_ST_UNIPERIF_TYPE_HDMI, SND_ST_UNIPERIF_TYPE_PCM, - SND_ST_UNIPERIF_TYPE_SPDIF + SND_ST_UNIPERIF_TYPE_SPDIF, + SND_ST_UNIPERIF_TYPE_TDM }; enum uniperif_state { @@ -1330,6 +1334,28 @@ struct sti_uniperiph_data { struct sti_uniperiph_dai dai_data; }; +static const struct snd_pcm_hardware uni_tdm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + + .channels_min = 1, + .channels_max = 32, + + .periods_min = 2, + .periods_max = 10, + + .period_bytes_min = 128, + .period_bytes_max = 64 * PAGE_SIZE, + .buffer_bytes_max = 256 * PAGE_SIZE +}; + /* uniperiph player*/ int uni_player_init(struct platform_device *pdev, struct uniperif *uni_player); From 44f948bdb175bf326911c9ba0e47389918161ce5 Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 7 Apr 2016 11:25:33 +0200 Subject: [PATCH 020/393] ASoC: sti: helper functions for unip tdm slots configuration - sti_uniperiph_set_tdm_slot: store tdm slot config in unip context - sti_uniperiph_get_tdm_word_pos: configure unip tdm slots pos regs Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/sti_uniperif.c | 90 ++++++++++++++++++++++++++++++++++++ sound/soc/sti/uniperif.h | 23 +++++++++ 2 files changed, 113 insertions(+) diff --git a/sound/soc/sti/sti_uniperif.c b/sound/soc/sti/sti_uniperif.c index 39bcefe5eea0..0c2474c16c11 100644 --- a/sound/soc/sti/sti_uniperif.c +++ b/sound/soc/sti/sti_uniperif.c @@ -10,6 +10,96 @@ #include "uniperif.h" +/* + * User frame size shall be 2, 4, 6 or 8 32-bits words length + * (i.e. 8, 16, 24 or 32 bytes) + * This constraint comes from allowed values for + * UNIPERIF_I2S_FMT_NUM_CH register + */ +#define UNIPERIF_MAX_FRAME_SZ 0x20 +#define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ) + +int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; + int i, frame_size, avail_slots; + + if (!UNIPERIF_TYPE_IS_TDM(uni)) { + dev_err(uni->dev, "cpu dai not in tdm mode\n"); + return -EINVAL; + } + + /* store info in unip context */ + uni->tdm_slot.slots = slots; + uni->tdm_slot.slot_width = slot_width; + /* unip is unidirectionnal */ + uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask; + + /* number of available timeslots */ + for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) { + if ((uni->tdm_slot.mask >> i) & 0x01) + avail_slots++; + } + uni->tdm_slot.avail_slots = avail_slots; + + /* frame size in bytes */ + frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8; + + /* check frame size is allowed */ + if ((frame_size > UNIPERIF_MAX_FRAME_SZ) || + (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) { + dev_err(uni->dev, "frame size not allowed: %d bytes\n", + frame_size); + return -EINVAL; + } + + return 0; +} + +int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, + unsigned int *word_pos) +{ + int slot_width = uni->tdm_slot.slot_width / 8; + int slots_num = uni->tdm_slot.slots; + unsigned int slots_mask = uni->tdm_slot.mask; + int i, j, k; + unsigned int word16_pos[4]; + + /* word16_pos: + * word16_pos[0] = WORDX_LSB + * word16_pos[1] = WORDX_MSB, + * word16_pos[2] = WORDX+1_LSB + * word16_pos[3] = WORDX+1_MSB + */ + + /* set unip word position */ + for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) { + if ((slots_mask >> i) & 0x01) { + word16_pos[j] = i * slot_width; + + if (slot_width == 4) { + word16_pos[j + 1] = word16_pos[j] + 2; + j++; + } + j++; + + if (j > 3) { + word_pos[k] = word16_pos[1] | + (word16_pos[0] << 8) | + (word16_pos[3] << 16) | + (word16_pos[2] << 24); + j = 0; + k++; + } + } + } + + return 0; +} + /* * sti_uniperiph_dai_create_ctrl * This function is used to create Ctrl associated to DAI but also pcm device. diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index 750eb5a07e6c..fb8e427754b6 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1270,6 +1270,14 @@ enum uniperif_iec958_encoding_mode { UNIPERIF_IEC958_ENCODING_MODE_ENCODED }; +enum uniperif_word_pos { + WORD_1_2, + WORD_3_4, + WORD_5_6, + WORD_7_8, + WORD_MAX +}; + struct uniperif_info { int id; /* instance value of the uniperipheral IP */ enum uniperif_type type; @@ -1281,6 +1289,13 @@ struct uniperif_iec958_settings { struct snd_aes_iec958 iec958; }; +struct dai_tdm_slot { + unsigned int mask; + int slots; + int slot_width; + unsigned int avail_slots; +}; + struct uniperif { /* System information */ struct uniperif_info *info; @@ -1317,6 +1332,7 @@ struct uniperif { /* dai properties */ unsigned int daifmt; + struct dai_tdm_slot tdm_slot; /* DAI callbacks */ const struct snd_soc_dai_ops *dai_ops; @@ -1373,4 +1389,11 @@ int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai); +int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width); + +int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, + unsigned int *word_pos); + #endif From 72199787662612a7747248f8b56bc5d42694538f Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 7 Apr 2016 11:25:34 +0200 Subject: [PATCH 021/393] ASoC: sti: helper functions to fix tdm runtime params Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/sti_uniperif.c | 46 ++++++++++++++++++++++++++++++++++++ sound/soc/sti/uniperif.h | 6 +++++ 2 files changed, 52 insertions(+) diff --git a/sound/soc/sti/sti_uniperif.c b/sound/soc/sti/sti_uniperif.c index 0c2474c16c11..d49badec9e62 100644 --- a/sound/soc/sti/sti_uniperif.c +++ b/sound/soc/sti/sti_uniperif.c @@ -59,6 +59,52 @@ int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct uniperif *uni = rule->private; + struct snd_interval t; + + t.min = uni->tdm_slot.avail_slots; + t.max = uni->tdm_slot.avail_slots; + t.openmin = 0; + t.openmax = 0; + t.integer = 0; + + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct uniperif *uni = rule->private; + struct snd_mask *maskp = hw_param_mask(params, rule->var); + u64 format; + + switch (uni->tdm_slot.slot_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; + break; + case 32: + format = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + dev_err(uni->dev, "format not supported: %d bits\n", + uni->tdm_slot.slot_width); + return -EINVAL; + } + + maskp->bits[0] &= (u_int32_t)format; + maskp->bits[1] &= (u_int32_t)(format >> 32); + /* clear remaining indexes */ + memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8); + + if (!maskp->bits[0] && !maskp->bits[1]) + return -EINVAL; + + return 0; +} + int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, unsigned int *word_pos) { diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index fb8e427754b6..17d5d1030741 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1396,4 +1396,10 @@ int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, unsigned int *word_pos); +int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule); + +int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule); + #endif From 8d8b1e2eddaef25ca0a18500dd9425638f1cfd02 Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 7 Apr 2016 11:25:35 +0200 Subject: [PATCH 022/393] ASoC: sti: unip player tdm mode here are the changes to enable player tdm mode: - When TDM_ENABLE is set to 1, the i2s format should be automatically configured. Unfortunately this is not the case (HW bug). Then, we shall force DATA_SIZE setting. - Compute the transfer size for tdm mode: transfer size = user frame size - Manage tdm slots configuration given in DT. - Don't use mclk-fs when unip in tdm mode; use tdm slot config to compute frame size and to set mclk rate. - Refine the hw param (channels & format) according to tdm slot config. Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/sti_uniperif.c | 8 ++- sound/soc/sti/uniperif.h | 11 ++++ sound/soc/sti/uniperif_player.c | 109 +++++++++++++++++++++++++++----- 3 files changed, 110 insertions(+), 18 deletions(-) diff --git a/sound/soc/sti/sti_uniperif.c b/sound/soc/sti/sti_uniperif.c index d49badec9e62..488ef4ed8fba 100644 --- a/sound/soc/sti/sti_uniperif.c +++ b/sound/soc/sti/sti_uniperif.c @@ -181,10 +181,16 @@ int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; struct snd_dmaengine_dai_dma_data *dma_data; int transfer_size; - transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; + if (uni->info->type == SND_ST_UNIPERIF_TYPE_TDM) + /* transfer size = user frame size (in 32-bits FIFO cell) */ + transfer_size = snd_soc_params_to_frame_size(params) / 32; + else + transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; dma_data = snd_soc_dai_get_dma_data(dai, substream); dma_data->maxburst = transfer_size; diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index 17d5d1030741..d0e24468478c 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1389,6 +1389,17 @@ int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai); +static inline int sti_uniperiph_get_user_frame_size( + struct snd_pcm_runtime *runtime) +{ + return (runtime->channels * snd_pcm_format_width(runtime->format) / 8); +} + +static inline int sti_uniperiph_get_unip_tdm_frame_size(struct uniperif *uni) +{ + return (uni->tdm_slot.slots * uni->tdm_slot.slot_width / 8); +} + int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width); diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index ab4310a1615e..ff2d73597ce3 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -435,18 +435,11 @@ static int uni_player_prepare_pcm(struct uniperif *player, /* Force slot width to 32 in I2S mode (HW constraint) */ if ((player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == - SND_SOC_DAIFMT_I2S) { + SND_SOC_DAIFMT_I2S) slot_width = 32; - } else { - switch (runtime->format) { - case SNDRV_PCM_FORMAT_S16_LE: - slot_width = 16; - break; - default: - slot_width = 32; - break; - } - } + else + slot_width = snd_pcm_format_width(runtime->format); + output_frame_size = slot_width * runtime->channels; clk_div = player->mclk / runtime->rate; @@ -521,7 +514,6 @@ static int uni_player_prepare_pcm(struct uniperif *player, SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player); SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); - SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player); /* No iec958 formatting as outputting to DAC */ SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player); @@ -529,6 +521,55 @@ static int uni_player_prepare_pcm(struct uniperif *player, return 0; } +static int uni_player_prepare_tdm(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int tdm_frame_size; /* unip tdm frame size in bytes */ + int user_frame_size; /* user tdm frame size in bytes */ + /* default unip TDM_WORD_POS_X_Y */ + unsigned int word_pos[4] = { + 0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A}; + int freq, ret; + + tdm_frame_size = + sti_uniperiph_get_unip_tdm_frame_size(player); + user_frame_size = + sti_uniperiph_get_user_frame_size(runtime); + + /* fix 16/0 format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player); + + /* number of words inserted on the TDM line */ + SET_UNIPERIF_I2S_FMT_NUM_CH(player, user_frame_size / 4 / 2); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player); + + /* Enable the tdm functionality */ + SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(player); + + /* number of 8 bits timeslots avail in unip tdm frame */ + SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(player, tdm_frame_size); + + /* set the timeslot allocation for words in FIFO */ + sti_uniperiph_get_tdm_word_pos(player, word_pos); + SET_UNIPERIF_TDM_WORD_POS(player, 1_2, word_pos[WORD_1_2]); + SET_UNIPERIF_TDM_WORD_POS(player, 3_4, word_pos[WORD_3_4]); + SET_UNIPERIF_TDM_WORD_POS(player, 5_6, word_pos[WORD_5_6]); + SET_UNIPERIF_TDM_WORD_POS(player, 7_8, word_pos[WORD_7_8]); + + /* set unip clk rate (not done vai set_sysclk ops) */ + freq = runtime->rate * tdm_frame_size * 8; + mutex_lock(&player->ctrl_lock); + ret = uni_player_clk_set_rate(player, freq); + if (!ret) + player->mclk = freq; + mutex_unlock(&player->ctrl_lock); + + return 0; +} + /* * ALSA uniperipheral iec958 controls */ @@ -659,11 +700,29 @@ static int uni_player_startup(struct snd_pcm_substream *substream, { struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); struct uniperif *player = priv->dai_data.uni; + int ret; + player->substream = substream; player->clk_adj = 0; - return 0; + if (!UNIPERIF_TYPE_IS_TDM(player)) + return 0; + + /* refine hw constraint in tdm mode */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + sti_uniperiph_fix_tdm_chan, + player, SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + sti_uniperiph_fix_tdm_format, + player, SNDRV_PCM_HW_PARAM_FORMAT, + -1); } static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id, @@ -673,7 +732,7 @@ static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id, struct uniperif *player = priv->dai_data.uni; int ret; - if (dir == SND_SOC_CLOCK_IN) + if (UNIPERIF_TYPE_IS_TDM(player) || (dir == SND_SOC_CLOCK_IN)) return 0; if (clk_id != 0) @@ -705,7 +764,13 @@ static int uni_player_prepare(struct snd_pcm_substream *substream, } /* Calculate transfer size (in fifo cells and bytes) for frame count */ - transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + if (player->info->type == SND_ST_UNIPERIF_TYPE_TDM) { + /* transfer size = user frame size (in 32 bits FIFO cell) */ + transfer_size = + sti_uniperiph_get_user_frame_size(runtime) / 4; + } else { + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + } /* Calculate number of empty cells available before asserting DREQ */ if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { @@ -739,6 +804,9 @@ static int uni_player_prepare(struct snd_pcm_substream *substream, case SND_ST_UNIPERIF_TYPE_SPDIF: ret = uni_player_prepare_iec958(player, runtime); break; + case SND_ST_UNIPERIF_TYPE_TDM: + ret = uni_player_prepare_tdm(player, runtime); + break; default: dev_err(player->dev, "invalid player type"); return -EINVAL; @@ -1008,6 +1076,8 @@ static int uni_player_parse_dt(struct platform_device *pdev, info->type = SND_ST_UNIPERIF_TYPE_PCM; else if (strcasecmp(mode, "spdif") == 0) info->type = SND_ST_UNIPERIF_TYPE_SPDIF; + else if (strcasecmp(mode, "tdm") == 0) + info->type = SND_ST_UNIPERIF_TYPE_TDM; else info->type = SND_ST_UNIPERIF_TYPE_NONE; @@ -1028,7 +1098,8 @@ static const struct snd_soc_dai_ops uni_player_dai_ops = { .trigger = uni_player_trigger, .hw_params = sti_uniperiph_dai_hw_params, .set_fmt = sti_uniperiph_dai_set_fmt, - .set_sysclk = uni_player_set_sysclk + .set_sysclk = uni_player_set_sysclk, + .set_tdm_slot = sti_uniperiph_set_tdm_slot }; int uni_player_init(struct platform_device *pdev, @@ -1038,7 +1109,6 @@ int uni_player_init(struct platform_device *pdev, player->dev = &pdev->dev; player->state = UNIPERIF_STATE_STOPPED; - player->hw = &uni_player_pcm_hw; player->dai_ops = &uni_player_dai_ops; ret = uni_player_parse_dt(pdev, player); @@ -1048,6 +1118,11 @@ int uni_player_init(struct platform_device *pdev, return ret; } + if (UNIPERIF_TYPE_IS_TDM(player)) + player->hw = &uni_tdm_hw; + else + player->hw = &uni_player_pcm_hw; + /* Get uniperif resource */ player->clk = of_clk_get(pdev->dev.of_node, 0); if (IS_ERR(player->clk)) From 82d4eb91ab1912cb9e8751b9aa0875af2ae36db2 Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 7 Apr 2016 11:25:36 +0200 Subject: [PATCH 023/393] ASoC: sti: unip reader tdm mode Here are the changes to enable reader tdm mode: - When TDM_ENABLE is set to 1, the i2s format should be automatically configured. Unfortunately this is not the case (HW bug). Then, we shall force DATA_SIZE setting. - Compute the transfer size for tdm mode: transfer size = user frame size - Manage tdm slots configuration given in DT. - Refine the hw param (channels & format) according to tdm slot config. Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/uniperif_reader.c | 229 +++++++++++++++++++++++--------- 1 file changed, 168 insertions(+), 61 deletions(-) diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c index 8a0eb2050169..eb74a328c928 100644 --- a/sound/soc/sti/uniperif_reader.c +++ b/sound/soc/sti/uniperif_reader.c @@ -73,55 +73,10 @@ static irqreturn_t uni_reader_irq_handler(int irq, void *dev_id) return ret; } -static int uni_reader_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int uni_reader_prepare_pcm(struct snd_pcm_runtime *runtime, + struct uniperif *reader) { - struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); - struct uniperif *reader = priv->dai_data.uni; - struct snd_pcm_runtime *runtime = substream->runtime; - int transfer_size, trigger_limit; int slot_width; - int count = 10; - - /* The reader should be stopped */ - if (reader->state != UNIPERIF_STATE_STOPPED) { - dev_err(reader->dev, "%s: invalid reader state %d", __func__, - reader->state); - return -EINVAL; - } - - /* Calculate transfer size (in fifo cells and bytes) for frame count */ - transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; - - /* Calculate number of empty cells available before asserting DREQ */ - if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) - trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; - else - /* - * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 - * FDMA_TRIGGER_LIMIT also controls when the state switches - * from OFF or STANDBY to AUDIO DATA. - */ - trigger_limit = transfer_size; - - /* Trigger limit must be an even number */ - if ((!trigger_limit % 2) || - (trigger_limit != 1 && transfer_size % 2) || - (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) { - dev_err(reader->dev, "invalid trigger limit %d", trigger_limit); - return -EINVAL; - } - - SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit); - - switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_IB_IF: - case SND_SOC_DAIFMT_NB_IF: - SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); - break; - default: - SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); - } /* Force slot width to 32 in I2S mode */ if ((reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) @@ -173,6 +128,109 @@ static int uni_reader_prepare(struct snd_pcm_substream *substream, return -EINVAL; } + /* Number of channels must be even */ + if ((runtime->channels % 2) || (runtime->channels < 2) || + (runtime->channels > 10)) { + dev_err(reader->dev, "%s: invalid nb of channels", __func__); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2); + SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); + + return 0; +} + +static int uni_reader_prepare_tdm(struct snd_pcm_runtime *runtime, + struct uniperif *reader) +{ + int frame_size; /* user tdm frame size in bytes */ + /* default unip TDM_WORD_POS_X_Y */ + unsigned int word_pos[4] = { + 0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A}; + + frame_size = sti_uniperiph_get_user_frame_size(runtime); + + /* fix 16/0 format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(reader); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(reader); + + /* number of words inserted on the TDM line */ + SET_UNIPERIF_I2S_FMT_NUM_CH(reader, frame_size / 4 / 2); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); + SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(reader); + + /* + * set the timeslots allocation for words in FIFO + * + * HW bug: (LSB word < MSB word) => this config is not possible + * So if we want (LSB word < MSB) word, then it shall be + * handled by user + */ + sti_uniperiph_get_tdm_word_pos(reader, word_pos); + SET_UNIPERIF_TDM_WORD_POS(reader, 1_2, word_pos[WORD_1_2]); + SET_UNIPERIF_TDM_WORD_POS(reader, 3_4, word_pos[WORD_3_4]); + SET_UNIPERIF_TDM_WORD_POS(reader, 5_6, word_pos[WORD_5_6]); + SET_UNIPERIF_TDM_WORD_POS(reader, 7_8, word_pos[WORD_7_8]); + + return 0; +} + +static int uni_reader_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + struct snd_pcm_runtime *runtime = substream->runtime; + int transfer_size, trigger_limit, ret; + int count = 10; + + /* The reader should be stopped */ + if (reader->state != UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state %d", __func__, + reader->state); + return -EINVAL; + } + + /* Calculate transfer size (in fifo cells and bytes) for frame count */ + if (reader->info->type == SND_ST_UNIPERIF_TYPE_TDM) { + /* transfer size = unip frame size (in 32 bits FIFO cell) */ + transfer_size = + sti_uniperiph_get_user_frame_size(runtime) / 4; + } else { + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + } + + /* Calculate number of empty cells available before asserting DREQ */ + if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; + else + /* + * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 + * FDMA_TRIGGER_LIMIT also controls when the state switches + * from OFF or STANDBY to AUDIO DATA. + */ + trigger_limit = transfer_size; + + /* Trigger limit must be an even number */ + if ((!trigger_limit % 2) || + (trigger_limit != 1 && transfer_size % 2) || + (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) { + dev_err(reader->dev, "invalid trigger limit %d", trigger_limit); + return -EINVAL; + } + + SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit); + + if (UNIPERIF_TYPE_IS_TDM(reader)) + ret = uni_reader_prepare_tdm(runtime, reader); + else + ret = uni_reader_prepare_pcm(runtime, reader); + if (ret) + return ret; + switch (reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); @@ -191,21 +249,26 @@ static int uni_reader_prepare(struct snd_pcm_substream *substream, return -EINVAL; } - SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); - - /* Data clocking (changing) on the rising edge */ - SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); - - /* Number of channels must be even */ - - if ((runtime->channels % 2) || (runtime->channels < 2) || - (runtime->channels > 10)) { - dev_err(reader->dev, "%s: invalid nb of channels", __func__); - return -EINVAL; + /* Data clocking (changing) on the rising/falling edge */ + switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); + break; + case SND_SOC_DAIFMT_NB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); + break; + case SND_SOC_DAIFMT_IB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader); + break; + case SND_SOC_DAIFMT_IB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader); + break; } - SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2); - /* Clear any pending interrupts */ SET_UNIPERIF_ITS_BCLR(reader, GET_UNIPERIF_ITS(reader)); @@ -293,6 +356,32 @@ static int uni_reader_trigger(struct snd_pcm_substream *substream, } } +static int uni_reader_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + int ret; + + if (!UNIPERIF_TYPE_IS_TDM(reader)) + return 0; + + /* refine hw constraint in tdm mode */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + sti_uniperiph_fix_tdm_chan, + reader, SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + sti_uniperiph_fix_tdm_format, + reader, SNDRV_PCM_HW_PARAM_FORMAT, + -1); +} + static void uni_reader_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -310,6 +399,7 @@ static int uni_reader_parse_dt(struct platform_device *pdev, { struct uniperif_info *info; struct device_node *node = pdev->dev.of_node; + const char *mode; /* Allocate memory for the info structure */ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); @@ -322,6 +412,17 @@ static int uni_reader_parse_dt(struct platform_device *pdev, return -EINVAL; } + /* Read the device mode property */ + if (of_property_read_string(node, "st,mode", &mode)) { + dev_err(&pdev->dev, "uniperipheral mode not defined"); + return -EINVAL; + } + + if (strcasecmp(mode, "tdm") == 0) + info->type = SND_ST_UNIPERIF_TYPE_TDM; + else + info->type = SND_ST_UNIPERIF_TYPE_PCM; + /* Save the info structure */ reader->info = info; @@ -329,11 +430,13 @@ static int uni_reader_parse_dt(struct platform_device *pdev, } static const struct snd_soc_dai_ops uni_reader_dai_ops = { + .startup = uni_reader_startup, .shutdown = uni_reader_shutdown, .prepare = uni_reader_prepare, .trigger = uni_reader_trigger, .hw_params = sti_uniperiph_dai_hw_params, .set_fmt = sti_uniperiph_dai_set_fmt, + .set_tdm_slot = sti_uniperiph_set_tdm_slot }; int uni_reader_init(struct platform_device *pdev, @@ -343,7 +446,6 @@ int uni_reader_init(struct platform_device *pdev, reader->dev = &pdev->dev; reader->state = UNIPERIF_STATE_STOPPED; - reader->hw = &uni_reader_pcm_hw; reader->dai_ops = &uni_reader_dai_ops; ret = uni_reader_parse_dt(pdev, reader); @@ -352,6 +454,11 @@ int uni_reader_init(struct platform_device *pdev, return ret; } + if (UNIPERIF_TYPE_IS_TDM(reader)) + reader->hw = &uni_tdm_hw; + else + reader->hw = &uni_reader_pcm_hw; + ret = devm_request_irq(&pdev->dev, reader->irq, uni_reader_irq_handler, IRQF_SHARED, dev_name(&pdev->dev), reader); From 4f62568c1fcf0f7da49e7c22bdd01645aa508e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Tue, 12 Apr 2016 22:06:34 +0930 Subject: [PATCH 024/393] fujitsu-laptop: Support radio LED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lifebook E734/E744/E754 has a LED which the manual calls "radio components indicator". It should be lit when any radio transmitter is enabled. Its state can be read and set using ACPI (FUNC interface, RFKILL method). Since the Lifebook E734/E744/E754 only has a button (as compared to a slider) for enabling/disabling radio transmitters, I believe the LED in question is meant to indicate whether all radio transmitters are currently on or off. However, pressing the radio toggle button does not automatically change the hardware state of the transmitters: it looks like this machine relies on soft rfkill. As for detecting whether the LED is present on a given machine, I had to resort to educated guesswork. I assumed this LED is present on all devices which have a radio toggle button instead of a slider. My Lifebook E744 holds 0x01010001 in BTNI. By comparing the bits and buttons with those of a Lifebook E8420 (BTNI=0x000F0101, has a slider), I put my money on bit 24 as the indicator of the radio toggle button being present. Furthermore, bit 24 is also clear on the S7020 which does not have the toggle button or an RF LED. Figuring out how the LED is controlled was more deterministic as all it took was decompiling the DSDT and taking a look at method S000 (the RFKILL method of the FUNC interface). The LED control method implemented here is unsuitable for use with "heavy" LED triggers, like phy0rx. Once blinking frequency achieves a certain level, the system hangs. Signed-off-by: Michał Kępień [jwoithe: Comment on bit 24 in BTNI, expanded commit msg] Signed-off-by: Jonathan Woithe [dvhart: Minor style and commit log adjustments] Signed-off-by: Darren Hart --- drivers/platform/x86/fujitsu-laptop.c | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index ffc84cc7b1c7..1cb39d00d014 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -107,6 +107,7 @@ #define KEYBOARD_LAMPS 0x100 #define LOGOLAMP_POWERON 0x2000 #define LOGOLAMP_ALWAYS 0x4000 +#define RADIO_LED_ON 0x20 #endif /* Hotkey details */ @@ -174,6 +175,7 @@ struct fujitsu_hotkey_t { int rfkill_state; int logolamp_registered; int kblamps_registered; + int radio_led_registered; }; static struct fujitsu_hotkey_t *fujitsu_hotkey; @@ -200,6 +202,16 @@ static struct led_classdev kblamps_led = { .brightness_get = kblamps_get, .brightness_set = kblamps_set }; + +static enum led_brightness radio_led_get(struct led_classdev *cdev); +static void radio_led_set(struct led_classdev *cdev, + enum led_brightness brightness); + +static struct led_classdev radio_led = { + .name = "fujitsu::radio_led", + .brightness_get = radio_led_get, + .brightness_set = radio_led_set +}; #endif #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG @@ -275,6 +287,15 @@ static void kblamps_set(struct led_classdev *cdev, call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF); } +static void radio_led_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + if (brightness >= LED_FULL) + call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, RADIO_LED_ON); + else + call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, 0x0); +} + static enum led_brightness logolamp_get(struct led_classdev *cdev) { enum led_brightness brightness = LED_OFF; @@ -299,6 +320,16 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev) return brightness; } + +static enum led_brightness radio_led_get(struct led_classdev *cdev) +{ + enum led_brightness brightness = LED_OFF; + + if (call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0) & RADIO_LED_ON) + brightness = LED_FULL; + + return brightness; +} #endif /* Hardware access for LCD brightness control */ @@ -895,6 +926,23 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) result); } } + + /* + * BTNI bit 24 seems to indicate the presence of a radio toggle + * button in place of a slide switch, and all such machines appear + * to also have an RF LED. Therefore use bit 24 as an indicator + * that an RF LED is present. + */ + if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) { + result = led_classdev_register(&fujitsu->pf_device->dev, + &radio_led); + if (result == 0) { + fujitsu_hotkey->radio_led_registered = 1; + } else { + pr_err("Could not register LED handler for radio LED, error %i\n", + result); + } + } #endif return result; @@ -921,6 +969,9 @@ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device) if (fujitsu_hotkey->kblamps_registered) led_classdev_unregister(&kblamps_led); + + if (fujitsu_hotkey->radio_led_registered) + led_classdev_unregister(&radio_led); #endif input_unregister_device(input); From 3ee15cac90e168fdea497a168a2e79acb1c4e612 Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Thu, 14 Apr 2016 15:29:35 +0200 Subject: [PATCH 025/393] ASoC: sti: select player for I2S/TDM TX bus By default, player#0 is connected to I2S/TDM TX bus. This patch connects player#1 to I2S/TDM TX bus. Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- sound/soc/sti/uniperif.h | 1 + sound/soc/sti/uniperif_player.c | 42 ++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h index d0e24468478c..eb9933c62ad6 100644 --- a/sound/soc/sti/uniperif.h +++ b/sound/soc/sti/uniperif.h @@ -1302,6 +1302,7 @@ struct uniperif { struct device *dev; int ver; /* IP version, used by register access macros */ struct regmap_field *clk_sel; + struct regmap_field *valid_sel; /* capabilities */ const struct snd_pcm_hardware *hw; diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index ff2d73597ce3..ee1c7c245bc7 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -21,7 +21,6 @@ /* sys config registers definitions */ #define SYS_CFG_AUDIO_GLUE 0xA4 -#define SYS_CFG_AUDI0_GLUE_PCM_CLKX 8 /* * Driver specific types. @@ -29,6 +28,7 @@ #define UNIPERIF_PLAYER_CLK_ADJ_MIN -999999 #define UNIPERIF_PLAYER_CLK_ADJ_MAX 1000000 +#define UNIPERIF_PLAYER_I2S_OUT 1 /* player id connected to I2S/TDM TX bus */ /* * Note: snd_pcm_hardware is linked to DMA controller but is declared here to @@ -1013,27 +1013,30 @@ static void uni_player_shutdown(struct snd_pcm_substream *substream, player->substream = NULL; } -static int uni_player_parse_dt_clk_glue(struct platform_device *pdev, - struct uniperif *player) +static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, + struct uniperif *player) { - int bit_offset; struct device_node *node = pdev->dev.of_node; struct regmap *regmap; - - bit_offset = SYS_CFG_AUDI0_GLUE_PCM_CLKX + player->info->id; + struct reg_field regfield[2] = { + /* PCM_CLK_SEL */ + REG_FIELD(SYS_CFG_AUDIO_GLUE, + 8 + player->info->id, + 8 + player->info->id), + /* PCMP_VALID_SEL */ + REG_FIELD(SYS_CFG_AUDIO_GLUE, 0, 1) + }; regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); - if (regmap) { - struct reg_field regfield = - REG_FIELD(SYS_CFG_AUDIO_GLUE, bit_offset, bit_offset); - - player->clk_sel = regmap_field_alloc(regmap, regfield); - } else { + if (!regmap) { dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n"); return -EINVAL; } + player->clk_sel = regmap_field_alloc(regmap, regfield[0]); + player->valid_sel = regmap_field_alloc(regmap, regfield[1]); + return 0; } @@ -1084,8 +1087,8 @@ static int uni_player_parse_dt(struct platform_device *pdev, /* Save the info structure */ player->info = info; - /* Get the PCM_CLK_SEL bit from audio-glue-ctrl SoC register */ - if (uni_player_parse_dt_clk_glue(pdev, player)) + /* Get PCM_CLK_SEL & PCMP_VALID_SEL from audio-glue-ctrl SoC reg */ + if (uni_player_parse_dt_audio_glue(pdev, player)) return -EINVAL; return 0; @@ -1139,6 +1142,17 @@ int uni_player_init(struct platform_device *pdev, } } + /* connect to I2S/TDM TX bus */ + if (player->valid_sel && + (player->info->id == UNIPERIF_PLAYER_I2S_OUT)) { + ret = regmap_field_write(player->valid_sel, player->info->id); + if (ret) { + dev_err(player->dev, + "%s: unable to connect to tdm bus", __func__); + return ret; + } + } + ret = devm_request_irq(&pdev->dev, player->irq, uni_player_irq_handler, IRQF_SHARED, dev_name(&pdev->dev), player); From ee4c879b53eb5d757661b9bc8fcdf1a3d2e5fdfb Mon Sep 17 00:00:00 2001 From: Moise Gergaud Date: Tue, 19 Apr 2016 11:24:45 +0200 Subject: [PATCH 026/393] ASoC: sti-asoc-card: update tdm mode - Add "TDM" in the st,mode property list - st,mode property is also mandatory for reader - add tdm playback dai-link example Signed-off-by: Moise Gergaud Acked-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- .../bindings/sound/st,sti-asoc-card.txt | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt b/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt index f546dd914812..4d9a83d9a017 100644 --- a/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt +++ b/Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt @@ -37,17 +37,18 @@ Required properties: - dai-name: DAI name that describes the IP. + - IP mode: IP working mode depending on associated codec. + "HDMI" connected to HDMI codec and support IEC HDMI formats (player only). + "SPDIF" connected to SPDIF codec and support SPDIF formats (player only). + "PCM" PCM standard mode for I2S or TDM bus. + "TDM" TDM mode for TDM bus. + Required properties ("st,sti-uni-player" compatibility only): - clocks: CPU_DAI IP clock source, listed in the same order than the CPU_DAI properties. - uniperiph-id: internal SOC IP instance ID. - - IP mode: IP working mode depending on associated codec. - "HDMI" connected to HDMI codec IP and IEC HDMI formats. - "SPDIF"connected to SPDIF codec and support SPDIF formats. - "PCM" PCM standard mode for I2S or TDM bus. - Optional properties: - pinctrl-0: defined for CPU_DAI@1 and CPU_DAI@4 to describe I2S PIOs for external codecs connection. @@ -56,6 +57,22 @@ Optional properties: Example: + sti_uni_player1: sti-uni-player@1 { + compatible = "st,sti-uni-player"; + status = "okay"; + #sound-dai-cells = <0>; + st,syscfg = <&syscfg_core>; + clocks = <&clk_s_d0_flexgen CLK_PCM_1>; + reg = <0x8D81000 0x158>; + interrupts = ; + dmas = <&fdma0 3 0 1>; + st,dai-name = "Uni Player #1 (I2S)"; + dma-names = "tx"; + st,uniperiph-id = <1>; + st,version = <5>; + st,mode = "TDM"; + }; + sti_uni_player2: sti-uni-player@2 { compatible = "st,sti-uni-player"; status = "okay"; @@ -99,6 +116,7 @@ Example: dma-names = "rx"; dai-name = "Uni Reader #1 (HDMI RX)"; version = <3>; + st,mode = "PCM"; }; 2) sti-sas-codec: internal audio codec IPs driver @@ -152,4 +170,20 @@ Example of audio card declaration: sound-dai = <&sti_sasg_codec 0>; }; }; + simple-audio-card,dai-link@2 { + /* TDM playback */ + format = "left_j"; + frame-inversion = <1>; + cpu { + sound-dai = <&sti_uni_player1>; + dai-tdm-slot-num = <16>; + dai-tdm-slot-width = <16>; + dai-tdm-slot-tx-mask = + <1 1 1 1 0 0 0 0 0 0 1 1 0 0 1 1>; + }; + + codec { + sound-dai = <&sti_sasg_codec 3>; + }; + }; }; From ec513886411e4fc47f98607a1bc79b72899710c4 Mon Sep 17 00:00:00 2001 From: Jeremy McDermond Date: Mon, 18 Apr 2016 17:24:04 -0700 Subject: [PATCH 027/393] ASoC: tlv320aic32x4: Change name of probe function The codec's probe function is named aic32x4_probe. This is going to conflict with later work to implement SPI support and separate out I2S into its own file. In line with other drivers in the tree, this function is renamed to aic32x4_codec_probe instead. Signed-off-by: Jeremy McDermond Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index f2d3191961e1..9d5b08b80d24 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -596,7 +596,7 @@ static struct snd_soc_dai_driver aic32x4_dai = { .symmetric_rates = 1, }; -static int aic32x4_probe(struct snd_soc_codec *codec) +static int aic32x4_codec_probe(struct snd_soc_codec *codec) { struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); u32 tmp_reg; @@ -655,7 +655,7 @@ static int aic32x4_probe(struct snd_soc_codec *codec) } static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = { - .probe = aic32x4_probe, + .probe = aic32x4_codec_probe, .set_bias_level = aic32x4_set_bias_level, .suspend_bias_off = true, From 3bcfd222f6f0c8758f369ce0db23fa3287db59a6 Mon Sep 17 00:00:00 2001 From: Jeremy McDermond Date: Mon, 18 Apr 2016 17:24:05 -0700 Subject: [PATCH 028/393] ASoC: tlv320aic32x4: Break out I2C support into separate module To prepare for abstracting adding SPI support, the I2C pieces needs to be in its own moudle. This patch moves common probe code into aic32x4_probe and common removal code into aic32x4_remove. It also creates a static regmap config structure to be copied in the I2C specific driver. Signed-off-by: Jeremy McDermond Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 7 ++- sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tlv320aic32x4-i2c.c | 74 +++++++++++++++++++++++++++ sound/soc/codecs/tlv320aic32x4.c | 76 +++++++++------------------- sound/soc/codecs/tlv320aic32x4.h | 7 +++ 5 files changed, 113 insertions(+), 53 deletions(-) create mode 100644 sound/soc/codecs/tlv320aic32x4-i2c.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 649e92a252ae..38d07c3f5a38 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -129,7 +129,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC23_SPI if SPI_MASTER select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC31XX if I2C - select SND_SOC_TLV320AIC32X4 if I2C + select SND_SOC_TLV320AIC32X4_I2C if I2C select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -769,6 +769,11 @@ config SND_SOC_TLV320AIC31XX config SND_SOC_TLV320AIC32X4 tristate +config SND_SOC_TLV320AIC32X4_I2C + tristate + depends on I2C + select SND_SOC_TLV320AIC32X4 + config SND_SOC_TLV320AIC3X tristate "Texas Instruments TLV320AIC3x CODECs" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 185a712a7fe7..7ce294ce3afd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -136,6 +136,7 @@ snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o +snd-soc-tlv320aic32x4-i2c-objs := tlv320aic32x4-i2c.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-ts3a227e-objs := ts3a227e.o @@ -342,6 +343,7 @@ obj-$(CONFIG_SND_SOC_TLV320AIC23_SPI) += snd-soc-tlv320aic23-spi.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC31XX) += snd-soc-tlv320aic31xx.o obj-$(CONFIG_SND_SOC_TLV320AIC32X4) += snd-soc-tlv320aic32x4.o +obj-$(CONFIG_SND_SOC_TLV320AIC32X4_I2C) += snd-soc-tlv320aic32x4-i2c.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TS3A227E) += snd-soc-ts3a227e.o diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c new file mode 100644 index 000000000000..59606cf3008f --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-i2c.c @@ -0,0 +1,74 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4-i2c.c + * + * Copyright 2011 NW Digital Radio + * + * Author: Jeremy McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include "tlv320aic32x4.h" + +static int aic32x4_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + struct regmap_config config; + + config = aic32x4_regmap_config; + config.reg_bits = 8; + config.val_bits = 8; + + regmap = devm_regmap_init_i2c(i2c, &config); + return aic32x4_probe(&i2c->dev, regmap); +} + +static int aic32x4_i2c_remove(struct i2c_client *i2c) +{ + return aic32x4_remove(&i2c->dev); +} + +static const struct i2c_device_id aic32x4_i2c_id[] = { + { "tlv320aic32x4", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id); + +static const struct of_device_id aic32x4_of_id[] = { + { .compatible = "ti,tlv320aic32x4", }, + { /* senitel */ } +}; +MODULE_DEVICE_TABLE(of, aic32x4_of_id); + +static struct i2c_driver aic32x4_i2c_driver = { + .driver = { + .name = "tlv320aic32x4", + .of_match_table = aic32x4_of_id, + }, + .probe = aic32x4_i2c_probe, + .remove = aic32x4_i2c_remove, + .id_table = aic32x4_i2c_id, +}; + +module_i2c_driver(aic32x4_i2c_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver I2C"); +MODULE_AUTHOR("Jeremy McDermond "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 9d5b08b80d24..c6b6d551f4fe 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -287,14 +286,12 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { }, }; -static const struct regmap_config aic32x4_regmap = { - .reg_bits = 8, - .val_bits = 8, - +const struct regmap_config aic32x4_regmap_config = { .max_register = AIC32X4_RMICPGAVOL, .ranges = aic32x4_regmap_pages, .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), }; +EXPORT_SYMBOL(aic32x4_regmap_config); static inline int aic32x4_get_divs(int mclk, int rate) { @@ -777,24 +774,22 @@ static int aic32x4_setup_regulators(struct device *dev, return ret; } -static int aic32x4_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +int aic32x4_probe(struct device *dev, struct regmap *regmap) { - struct aic32x4_pdata *pdata = i2c->dev.platform_data; struct aic32x4_priv *aic32x4; - struct device_node *np = i2c->dev.of_node; + struct aic32x4_pdata *pdata = dev->platform_data; + struct device_node *np = dev->of_node; int ret; - aic32x4 = devm_kzalloc(&i2c->dev, sizeof(struct aic32x4_priv), + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), GFP_KERNEL); if (aic32x4 == NULL) return -ENOMEM; - aic32x4->regmap = devm_regmap_init_i2c(i2c, &aic32x4_regmap); - if (IS_ERR(aic32x4->regmap)) - return PTR_ERR(aic32x4->regmap); - - i2c_set_clientdata(i2c, aic32x4); + dev_set_drvdata(dev, aic32x4); if (pdata) { aic32x4->power_cfg = pdata->power_cfg; @@ -804,7 +799,7 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c, } else if (np) { ret = aic32x4_parse_dt(aic32x4, np); if (ret) { - dev_err(&i2c->dev, "Failed to parse DT node\n"); + dev_err(dev, "Failed to parse DT node\n"); return ret; } } else { @@ -814,71 +809,48 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c, aic32x4->rstn_gpio = -1; } - aic32x4->mclk = devm_clk_get(&i2c->dev, "mclk"); + aic32x4->mclk = devm_clk_get(dev, "mclk"); if (IS_ERR(aic32x4->mclk)) { - dev_err(&i2c->dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); + dev_err(dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); return PTR_ERR(aic32x4->mclk); } if (gpio_is_valid(aic32x4->rstn_gpio)) { - ret = devm_gpio_request_one(&i2c->dev, aic32x4->rstn_gpio, + ret = devm_gpio_request_one(dev, aic32x4->rstn_gpio, GPIOF_OUT_INIT_LOW, "tlv320aic32x4 rstn"); if (ret != 0) return ret; } - ret = aic32x4_setup_regulators(&i2c->dev, aic32x4); + ret = aic32x4_setup_regulators(dev, aic32x4); if (ret) { - dev_err(&i2c->dev, "Failed to setup regulators\n"); + dev_err(dev, "Failed to setup regulators\n"); return ret; } - ret = snd_soc_register_codec(&i2c->dev, + ret = snd_soc_register_codec(dev, &soc_codec_dev_aic32x4, &aic32x4_dai, 1); if (ret) { - dev_err(&i2c->dev, "Failed to register codec\n"); + dev_err(dev, "Failed to register codec\n"); aic32x4_disable_regulators(aic32x4); return ret; } - i2c_set_clientdata(i2c, aic32x4); - return 0; } +EXPORT_SYMBOL(aic32x4_probe); -static int aic32x4_i2c_remove(struct i2c_client *client) +int aic32x4_remove(struct device *dev) { - struct aic32x4_priv *aic32x4 = i2c_get_clientdata(client); + struct aic32x4_priv *aic32x4 = dev_get_drvdata(dev); aic32x4_disable_regulators(aic32x4); - snd_soc_unregister_codec(&client->dev); + snd_soc_unregister_codec(dev); + return 0; } - -static const struct i2c_device_id aic32x4_i2c_id[] = { - { "tlv320aic32x4", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id); - -static const struct of_device_id aic32x4_of_id[] = { - { .compatible = "ti,tlv320aic32x4", }, - { /* senitel */ } -}; -MODULE_DEVICE_TABLE(of, aic32x4_of_id); - -static struct i2c_driver aic32x4_i2c_driver = { - .driver = { - .name = "tlv320aic32x4", - .of_match_table = aic32x4_of_id, - }, - .probe = aic32x4_i2c_probe, - .remove = aic32x4_i2c_remove, - .id_table = aic32x4_i2c_id, -}; - -module_i2c_driver(aic32x4_i2c_driver); +EXPORT_SYMBOL(aic32x4_remove); MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver"); MODULE_AUTHOR("Javier Martin "); diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h index 995f033a855d..a197dd51addc 100644 --- a/sound/soc/codecs/tlv320aic32x4.h +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -10,6 +10,13 @@ #ifndef _TLV320AIC32X4_H #define _TLV320AIC32X4_H +struct device; +struct regmap_config; + +extern const struct regmap_config aic32x4_regmap_config; +int aic32x4_probe(struct device *dev, struct regmap *regmap); +int aic32x4_remove(struct device *dev); + /* tlv320aic32x4 register space (in decimal to match datasheet) */ #define AIC32X4_PAGE1 128 From 125bc681bc5d8183594e30492f0345a61ab3cc94 Mon Sep 17 00:00:00 2001 From: Jeremy McDermond Date: Mon, 18 Apr 2016 17:24:06 -0700 Subject: [PATCH 029/393] ASoC: tlv320aic32x4: Add SPI support Add support for running the tlv32x4 control channel over SPI rather than I2C. Signed-off-by: Jeremy McDermond Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 6 +++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tlv320aic32x4-spi.c | 76 ++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 sound/soc/codecs/tlv320aic32x4-spi.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 38d07c3f5a38..3cc56e7d6e78 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -130,6 +130,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC31XX if I2C select SND_SOC_TLV320AIC32X4_I2C if I2C + select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -774,6 +775,11 @@ config SND_SOC_TLV320AIC32X4_I2C depends on I2C select SND_SOC_TLV320AIC32X4 +config SND_SOC_TLV320AIC32X4_SPI + tristate + depends on SPI_MASTER + select SND_SOC_TLV320AIC32X4 + config SND_SOC_TLV320AIC3X tristate "Texas Instruments TLV320AIC3x CODECs" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 7ce294ce3afd..6b59bdb20aa1 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -137,6 +137,7 @@ snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o snd-soc-tlv320aic32x4-i2c-objs := tlv320aic32x4-i2c.o +snd-soc-tlv320aic32x4-spi-objs := tlv320aic32x4-spi.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-ts3a227e-objs := ts3a227e.o @@ -344,6 +345,7 @@ obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC31XX) += snd-soc-tlv320aic31xx.o obj-$(CONFIG_SND_SOC_TLV320AIC32X4) += snd-soc-tlv320aic32x4.o obj-$(CONFIG_SND_SOC_TLV320AIC32X4_I2C) += snd-soc-tlv320aic32x4-i2c.o +obj-$(CONFIG_SND_SOC_TLV320AIC32X4_SPI) += snd-soc-tlv320aic32x4-spi.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TS3A227E) += snd-soc-ts3a227e.o diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c new file mode 100644 index 000000000000..724fcdd491b2 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-spi.c @@ -0,0 +1,76 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4-spi.c + * + * Copyright 2011 NW Digital Radio + * + * Author: Jeremy McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include "tlv320aic32x4.h" + +static int aic32x4_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + struct regmap_config config; + + config = aic32x4_regmap_config; + config.reg_bits = 7; + config.pad_bits = 1; + config.val_bits = 8; + config.read_flag_mask = 0x01; + + regmap = devm_regmap_init_spi(spi, &config); + return aic32x4_probe(&spi->dev, regmap); +} + +static int aic32x4_spi_remove(struct spi_device *spi) +{ + return aic32x4_remove(&spi->dev); +} + +static const struct spi_device_id aic32x4_spi_id[] = { + { "tlv320aic32x4", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, aic32x4_spi_id); + +static const struct of_device_id aic32x4_of_id[] = { + { .compatible = "ti,tlv320aic32x4", }, + { /* senitel */ } +}; +MODULE_DEVICE_TABLE(of, aic32x4_of_id); + +static struct spi_driver aic32x4_spi_driver = { + .driver = { + .name = "tlv320aic32x4", + .owner = THIS_MODULE, + .of_match_table = aic32x4_of_id, + }, + .probe = aic32x4_spi_probe, + .remove = aic32x4_spi_remove, + .id_table = aic32x4_spi_id, +}; + +module_spi_driver(aic32x4_spi_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver SPI"); +MODULE_AUTHOR("Jeremy McDermond "); +MODULE_LICENSE("GPL"); From f5cc17720b61af382a053d49a4a14c3d14857a5b Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Tue, 19 Apr 2016 18:00:20 +0200 Subject: [PATCH 030/393] ASoC: tlv320aix31xx: Add ACPI match for Lenovo 100S The Lenovo 100S netbook has a codec controller for which there is a driver, but doesn't know how to access the device. This adds the necessary ACPI table for the driver to find the device. Device (TTLV) { Name (_ADR, Zero) // _ADR: Address Name (_HID, "10TI3100") // _HID: Hardware ID Name (_CID, "10TI3100") // _CID: Compatible ID Name (_DDN, "TI TLV320AIC3100 Codec Controller ") // _DDN: DOS Device Name Name (_UID, One) // _UID: Unique ID Signed-off-by: Bastien Nocera Tested-by: Jan Schmidt Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic31xx.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index ee4def4f819f..3c5e1df01c19 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1280,10 +1281,19 @@ static const struct i2c_device_id aic31xx_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, aic31xx_i2c_id); +#ifdef CONFIG_ACPI +static const struct acpi_device_id aic31xx_acpi_match[] = { + { "10TI3100", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, aic31xx_acpi_match); +#endif + static struct i2c_driver aic31xx_i2c_driver = { .driver = { .name = "tlv320aic31xx-codec", .of_match_table = of_match_ptr(tlv320aic31xx_of_match), + .acpi_match_table = ACPI_PTR(aic31xx_acpi_match), }, .probe = aic31xx_i2c_probe, .remove = aic31xx_i2c_remove, From 041f9d336f28d3a45b31799bb8b5b2e1fa322321 Mon Sep 17 00:00:00 2001 From: Jeremy McDermond Date: Tue, 19 Apr 2016 09:59:04 -0700 Subject: [PATCH 031/393] ASoC: tlv320aic32x4: Add 96k sample rate The TLV320AIC32x4 series supports 96ksps rates in hardware. This patch adds the necessary PLL divider values and clock settings to the table to make 96ksps work. Signed-off-by: Jeremy McDermond Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index c6b6d551f4fe..2f8480c93b3c 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -159,7 +159,10 @@ static const struct aic32x4_rate_divs aic32x4_divs[] = { /* 48k rate */ {AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4}, {AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4}, - {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4} + {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}, + + /* 96k rate */ + {AIC32X4_FREQ_25000000, 96000, 2, 7, 8643, 64, 4, 4, 64, 4, 4, 1}, }; static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { @@ -564,7 +567,7 @@ static int aic32x4_set_bias_level(struct snd_soc_codec *codec, return 0; } -#define AIC32X4_RATES SNDRV_PCM_RATE_8000_48000 +#define AIC32X4_RATES SNDRV_PCM_RATE_8000_96000 #define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) From 2ebdf684082fa9ad924df1b2f80653920c7ca097 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 19 Apr 2016 18:08:00 -0700 Subject: [PATCH 032/393] ASoC: rsnd: Remove CLK_IS_ROOT This flag is a no-op now (see commit 47b0eeb3dc8a "clk: Deprecate CLK_IS_ROOT", 2016-02-02) so remove it. Signed-off-by: Stephen Boyd Acked-by: Kuninori Morimoto Signed-off-by: Mark Brown --- sound/soc/sh/rcar/adg.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index 606399de684d..49354d17ea55 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -492,9 +492,7 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, */ if (!count) { clk = clk_register_fixed_rate(dev, clkout_name[CLKOUT], - parent_clk_name, - (parent_clk_name) ? - 0 : CLK_IS_ROOT, req_rate); + parent_clk_name, 0, req_rate); if (!IS_ERR(clk)) { adg->clkout[CLKOUT] = clk; of_clk_add_provider(np, of_clk_src_simple_get, clk); @@ -506,9 +504,7 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, else { for (i = 0; i < CLKOUTMAX; i++) { clk = clk_register_fixed_rate(dev, clkout_name[i], - parent_clk_name, - (parent_clk_name) ? - 0 : CLK_IS_ROOT, + parent_clk_name, 0, req_rate); if (!IS_ERR(clk)) { adg->onecell.clks = adg->clkout; From b84fff5afb16627a8973256992f3951ac3e90d84 Mon Sep 17 00:00:00 2001 From: Mengdong Lin Date: Tue, 19 Apr 2016 13:12:43 +0800 Subject: [PATCH 033/393] ASoC: topology: Set the link ID when creating a FE DAI link Topology will set the link's generic id when creating a FE link. Device drivers can check the id for link specific initialization. Signed-off-by: Mengdong Lin Signed-off-by: Mark Brown --- include/uapi/sound/asoc.h | 2 +- sound/soc/soc-topology.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/uapi/sound/asoc.h b/include/uapi/sound/asoc.h index db86b447b515..e4701a3c6331 100644 --- a/include/uapi/sound/asoc.h +++ b/include/uapi/sound/asoc.h @@ -423,7 +423,7 @@ struct snd_soc_tplg_pcm { __le32 size; /* in bytes of this structure */ char pcm_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; char dai_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; - __le32 pcm_id; /* unique ID - used to match */ + __le32 pcm_id; /* unique ID - used to match with DAI link */ __le32 dai_id; /* unique ID - used to match */ __le32 playback; /* supports playback mode */ __le32 capture; /* supports capture mode */ diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 1cf94d7fb9f4..bdbfcef4c319 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1598,6 +1598,7 @@ static int soc_tplg_link_create(struct soc_tplg *tplg, link->name = pcm->pcm_name; link->stream_name = pcm->pcm_name; + link->id = pcm->pcm_id; /* pass control to component driver for optional further init */ ret = soc_tplg_dai_link_load(tplg, link); From 67d1c21e37301ca3cea3705951950ce21f2723e1 Mon Sep 17 00:00:00 2001 From: Guneshwor Singh Date: Tue, 19 Apr 2016 13:12:50 +0800 Subject: [PATCH 034/393] ASoC: topology: Set CPU DAI name and enable DPCM by default for FE link When creating a FE link, the cpu_dai_name will come from topology and dpcm will be enabled by default. Signed-off-by: Mengdong Lin Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index bdbfcef4c319..ca5f82885031 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1586,6 +1586,7 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg, return snd_soc_register_dai(tplg->comp, dai_drv); } +/* create the FE DAI link */ static int soc_tplg_link_create(struct soc_tplg *tplg, struct snd_soc_tplg_pcm *pcm) { @@ -1600,6 +1601,15 @@ static int soc_tplg_link_create(struct soc_tplg *tplg, link->stream_name = pcm->pcm_name; link->id = pcm->pcm_id; + link->cpu_dai_name = pcm->dai_name; + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + + /* enable DPCM */ + link->dynamic = 1; + link->dpcm_playback = pcm->playback; + link->dpcm_capture = pcm->capture; + /* pass control to component driver for optional further init */ ret = soc_tplg_dai_link_load(tplg, link); if (ret < 0) { From 314f1dc140844a73034e285ddac92b9e1ffecb5b Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 11:32:45 +0300 Subject: [PATCH 035/393] iommu/vt-d: refactoring of deferred flush entries Currently, deferred flushes' info is striped between several lists in the flush tables. Instead, move all information about a specific flush to a single entry in this table. This patch does not introduce any functional change. Signed-off-by: Omer Peleg [mad@cs.technion.ac.il: rebased and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin Signed-off-by: David Woodhouse --- drivers/iommu/intel-iommu.c | 52 ++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index e1852e845d21..ec562078247f 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -458,15 +458,19 @@ static void flush_unmaps_timeout(unsigned long data); static DEFINE_TIMER(unmap_timer, flush_unmaps_timeout, 0, 0); -#define HIGH_WATER_MARK 250 -struct deferred_flush_tables { - int next; - struct iova *iova[HIGH_WATER_MARK]; - struct dmar_domain *domain[HIGH_WATER_MARK]; - struct page *freelist[HIGH_WATER_MARK]; +struct deferred_flush_entry { + struct iova *iova; + struct dmar_domain *domain; + struct page *freelist; }; -static struct deferred_flush_tables *deferred_flush; +#define HIGH_WATER_MARK 250 +struct deferred_flush_table { + int next; + struct deferred_flush_entry entries[HIGH_WATER_MARK]; +}; + +static struct deferred_flush_table *deferred_flush; /* bitmap for indexing intel_iommus */ static int g_num_of_iommus; @@ -3111,7 +3115,7 @@ static int __init init_dmars(void) } deferred_flush = kzalloc(g_num_of_iommus * - sizeof(struct deferred_flush_tables), GFP_KERNEL); + sizeof(struct deferred_flush_table), GFP_KERNEL); if (!deferred_flush) { ret = -ENOMEM; goto free_g_iommus; @@ -3518,22 +3522,25 @@ static void flush_unmaps(void) DMA_TLB_GLOBAL_FLUSH); for (j = 0; j < deferred_flush[i].next; j++) { unsigned long mask; - struct iova *iova = deferred_flush[i].iova[j]; - struct dmar_domain *domain = deferred_flush[i].domain[j]; + struct deferred_flush_entry *entry = + &deferred_flush->entries[j]; + struct iova *iova = entry->iova; + struct dmar_domain *domain = entry->domain; + struct page *freelist = entry->freelist; /* On real hardware multiple invalidations are expensive */ if (cap_caching_mode(iommu->cap)) iommu_flush_iotlb_psi(iommu, domain, iova->pfn_lo, iova_size(iova), - !deferred_flush[i].freelist[j], 0); + !freelist, 0); else { mask = ilog2(mm_to_dma_pfn(iova_size(iova))); - iommu_flush_dev_iotlb(deferred_flush[i].domain[j], + iommu_flush_dev_iotlb(domain, (uint64_t)iova->pfn_lo << PAGE_SHIFT, mask); } - __free_iova(&deferred_flush[i].domain[j]->iovad, iova); - if (deferred_flush[i].freelist[j]) - dma_free_pagelist(deferred_flush[i].freelist[j]); + __free_iova(&domain->iovad, iova); + if (freelist) + dma_free_pagelist(freelist); } deferred_flush[i].next = 0; } @@ -3553,8 +3560,9 @@ static void flush_unmaps_timeout(unsigned long data) static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *freelist) { unsigned long flags; - int next, iommu_id; + int entry_id, iommu_id; struct intel_iommu *iommu; + struct deferred_flush_entry *entry; spin_lock_irqsave(&async_umap_flush_lock, flags); if (list_size == HIGH_WATER_MARK) @@ -3563,11 +3571,13 @@ static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *f iommu = domain_get_iommu(dom); iommu_id = iommu->seq_id; - next = deferred_flush[iommu_id].next; - deferred_flush[iommu_id].domain[next] = dom; - deferred_flush[iommu_id].iova[next] = iova; - deferred_flush[iommu_id].freelist[next] = freelist; - deferred_flush[iommu_id].next++; + entry_id = deferred_flush[iommu_id].next; + ++(deferred_flush[iommu_id].next); + + entry = &deferred_flush[iommu_id].entries[entry_id]; + entry->domain = dom; + entry->iova = iova; + entry->freelist = freelist; if (!timer_on) { mod_timer(&unmap_timer, jiffies + msecs_to_jiffies(10)); From aa4732406e1290dd18a8d2078977996c152a4be7 Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 11:33:02 +0300 Subject: [PATCH 036/393] iommu/vt-d: per-cpu deferred invalidation queues The IOMMU's IOTLB invalidation is a costly process. When iommu mode is not set to "strict", it is done asynchronously. Current code amortizes the cost of invalidating IOTLB entries by batching all the invalidations in the system and performing a single global invalidation instead. The code queues pending invalidations in a global queue that is accessed under the global "async_umap_flush_lock" spinlock, which can result is significant spinlock contention. This patch splits this deferred queue into multiple per-cpu deferred queues, and thus gets rid of the "async_umap_flush_lock" and its contention. To keep existing deferred invalidation behavior, it still invalidates the pending invalidations of all CPUs whenever a CPU reaches its watermark or a timeout occurs. Signed-off-by: Omer Peleg [mad@cs.technion.ac.il: rebased, cleaned up and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin Signed-off-by: David Woodhouse --- drivers/iommu/intel-iommu.c | 133 +++++++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 41 deletions(-) diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index ec562078247f..a64b6f3b9a66 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -456,8 +457,6 @@ static LIST_HEAD(dmar_rmrr_units); static void flush_unmaps_timeout(unsigned long data); -static DEFINE_TIMER(unmap_timer, flush_unmaps_timeout, 0, 0); - struct deferred_flush_entry { struct iova *iova; struct dmar_domain *domain; @@ -470,17 +469,19 @@ struct deferred_flush_table { struct deferred_flush_entry entries[HIGH_WATER_MARK]; }; -static struct deferred_flush_table *deferred_flush; +struct deferred_flush_data { + spinlock_t lock; + int timer_on; + struct timer_list timer; + long size; + struct deferred_flush_table *tables; +}; + +DEFINE_PER_CPU(struct deferred_flush_data, deferred_flush); /* bitmap for indexing intel_iommus */ static int g_num_of_iommus; -static DEFINE_SPINLOCK(async_umap_flush_lock); -static LIST_HEAD(unmaps_to_do); - -static int timer_on; -static long list_size; - static void domain_exit(struct dmar_domain *domain); static void domain_remove_dev_info(struct dmar_domain *domain); static void dmar_remove_one_dev_info(struct dmar_domain *domain, @@ -1922,8 +1923,12 @@ static void domain_exit(struct dmar_domain *domain) return; /* Flush any lazy unmaps that may reference this domain */ - if (!intel_iommu_strict) - flush_unmaps_timeout(0); + if (!intel_iommu_strict) { + int cpu; + + for_each_possible_cpu(cpu) + flush_unmaps_timeout(cpu); + } /* Remove associated devices and clear attached or cached domains */ rcu_read_lock(); @@ -3081,7 +3086,7 @@ static int __init init_dmars(void) bool copied_tables = false; struct device *dev; struct intel_iommu *iommu; - int i, ret; + int i, ret, cpu; /* * for each drhd @@ -3114,11 +3119,20 @@ static int __init init_dmars(void) goto error; } - deferred_flush = kzalloc(g_num_of_iommus * - sizeof(struct deferred_flush_table), GFP_KERNEL); - if (!deferred_flush) { - ret = -ENOMEM; - goto free_g_iommus; + for_each_possible_cpu(cpu) { + struct deferred_flush_data *dfd = per_cpu_ptr(&deferred_flush, + cpu); + + dfd->tables = kzalloc(g_num_of_iommus * + sizeof(struct deferred_flush_table), + GFP_KERNEL); + if (!dfd->tables) { + ret = -ENOMEM; + goto free_g_iommus; + } + + spin_lock_init(&dfd->lock); + setup_timer(&dfd->timer, flush_unmaps_timeout, cpu); } for_each_active_iommu(iommu, drhd) { @@ -3295,8 +3309,9 @@ static int __init init_dmars(void) disable_dmar_iommu(iommu); free_dmar_iommu(iommu); } - kfree(deferred_flush); free_g_iommus: + for_each_possible_cpu(cpu) + kfree(per_cpu_ptr(&deferred_flush, cpu)->tables); kfree(g_iommus); error: return ret; @@ -3501,29 +3516,31 @@ static dma_addr_t intel_map_page(struct device *dev, struct page *page, dir, *dev->dma_mask); } -static void flush_unmaps(void) +static void flush_unmaps(struct deferred_flush_data *flush_data) { int i, j; - timer_on = 0; + flush_data->timer_on = 0; /* just flush them all */ for (i = 0; i < g_num_of_iommus; i++) { struct intel_iommu *iommu = g_iommus[i]; + struct deferred_flush_table *flush_table = + &flush_data->tables[i]; if (!iommu) continue; - if (!deferred_flush[i].next) + if (!flush_table->next) continue; /* In caching mode, global flushes turn emulation expensive */ if (!cap_caching_mode(iommu->cap)) iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH); - for (j = 0; j < deferred_flush[i].next; j++) { + for (j = 0; j < flush_table->next; j++) { unsigned long mask; struct deferred_flush_entry *entry = - &deferred_flush->entries[j]; + &flush_table->entries[j]; struct iova *iova = entry->iova; struct dmar_domain *domain = entry->domain; struct page *freelist = entry->freelist; @@ -3542,19 +3559,20 @@ static void flush_unmaps(void) if (freelist) dma_free_pagelist(freelist); } - deferred_flush[i].next = 0; + flush_table->next = 0; } - list_size = 0; + flush_data->size = 0; } -static void flush_unmaps_timeout(unsigned long data) +static void flush_unmaps_timeout(unsigned long cpuid) { + struct deferred_flush_data *flush_data = per_cpu_ptr(&deferred_flush, cpuid); unsigned long flags; - spin_lock_irqsave(&async_umap_flush_lock, flags); - flush_unmaps(); - spin_unlock_irqrestore(&async_umap_flush_lock, flags); + spin_lock_irqsave(&flush_data->lock, flags); + flush_unmaps(flush_data); + spin_unlock_irqrestore(&flush_data->lock, flags); } static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *freelist) @@ -3563,28 +3581,44 @@ static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *f int entry_id, iommu_id; struct intel_iommu *iommu; struct deferred_flush_entry *entry; + struct deferred_flush_data *flush_data; + unsigned int cpuid; - spin_lock_irqsave(&async_umap_flush_lock, flags); - if (list_size == HIGH_WATER_MARK) - flush_unmaps(); + cpuid = get_cpu(); + flush_data = per_cpu_ptr(&deferred_flush, cpuid); + + /* Flush all CPUs' entries to avoid deferring too much. If + * this becomes a bottleneck, can just flush us, and rely on + * flush timer for the rest. + */ + if (flush_data->size == HIGH_WATER_MARK) { + int cpu; + + for_each_online_cpu(cpu) + flush_unmaps_timeout(cpu); + } + + spin_lock_irqsave(&flush_data->lock, flags); iommu = domain_get_iommu(dom); iommu_id = iommu->seq_id; - entry_id = deferred_flush[iommu_id].next; - ++(deferred_flush[iommu_id].next); + entry_id = flush_data->tables[iommu_id].next; + ++(flush_data->tables[iommu_id].next); - entry = &deferred_flush[iommu_id].entries[entry_id]; + entry = &flush_data->tables[iommu_id].entries[entry_id]; entry->domain = dom; entry->iova = iova; entry->freelist = freelist; - if (!timer_on) { - mod_timer(&unmap_timer, jiffies + msecs_to_jiffies(10)); - timer_on = 1; + if (!flush_data->timer_on) { + mod_timer(&flush_data->timer, jiffies + msecs_to_jiffies(10)); + flush_data->timer_on = 1; } - list_size++; - spin_unlock_irqrestore(&async_umap_flush_lock, flags); + flush_data->size++; + spin_unlock_irqrestore(&flush_data->lock, flags); + + put_cpu(); } static void intel_unmap(struct device *dev, dma_addr_t dev_addr) @@ -4508,6 +4542,23 @@ static struct notifier_block intel_iommu_memory_nb = { .priority = 0 }; +static int intel_iommu_cpu_notifier(struct notifier_block *nfb, + unsigned long action, void *v) +{ + unsigned int cpu = (unsigned long)v; + + switch (action) { + case CPU_DEAD: + case CPU_DEAD_FROZEN: + flush_unmaps_timeout(cpu); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block intel_iommu_cpu_nb = { + .notifier_call = intel_iommu_cpu_notifier, +}; static ssize_t intel_iommu_show_version(struct device *dev, struct device_attribute *attr, @@ -4641,7 +4692,6 @@ int __init intel_iommu_init(void) up_write(&dmar_global_lock); pr_info("Intel(R) Virtualization Technology for Directed I/O\n"); - init_timer(&unmap_timer); #ifdef CONFIG_SWIOTLB swiotlb = 0; #endif @@ -4658,6 +4708,7 @@ int __init intel_iommu_init(void) bus_register_notifier(&pci_bus_type, &device_nb); if (si_domain && !hw_pass_through) register_memory_notifier(&intel_iommu_memory_nb); + register_hotcpu_notifier(&intel_iommu_cpu_nb); intel_iommu_enabled = 1; From f5c0c08b1e0976cb493d503108f1b897ce58bc5d Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 11:33:11 +0300 Subject: [PATCH 037/393] iommu/vt-d: correct flush_unmaps pfn usage Change flush_unmaps() to correctly pass iommu_flush_iotlb_psi() dma addresses. (x86_64 mm and dma have the same size for pages at the moment, but this usage improves consistency.) Signed-off-by: Omer Peleg [mad@cs.technion.ac.il: rebased and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin Signed-off-by: David Woodhouse --- drivers/iommu/intel-iommu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index a64b6f3b9a66..6aacfa4eee2a 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -3548,7 +3548,8 @@ static void flush_unmaps(struct deferred_flush_data *flush_data) /* On real hardware multiple invalidations are expensive */ if (cap_caching_mode(iommu->cap)) iommu_flush_iotlb_psi(iommu, domain, - iova->pfn_lo, iova_size(iova), + mm_to_dma_pfn(iova->pfn_lo), + mm_to_dma_pfn(iova_size(iova)), !freelist, 0); else { mask = ilog2(mm_to_dma_pfn(iova_size(iova))); From 769530e4ba4898748a293592f0920275b40cbb93 Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 11:33:25 +0300 Subject: [PATCH 038/393] iommu/vt-d: only unmap mapped entries Current unmap implementation unmaps the entire area covered by the IOVA range, which is a power-of-2 aligned region. The corresponding map, however, only maps those pages originally mapped by the user. This discrepancy can lead to unmapping of already unmapped entries, which is unneeded work. With this patch, only mapped pages are unmapped. This is also a baseline for a map/unmap implementation based on IOVAs and not iova structures, which will allow caching. Signed-off-by: Omer Peleg [mad@cs.technion.ac.il: rebased and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin Signed-off-by: David Woodhouse --- drivers/iommu/intel-iommu.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index 6aacfa4eee2a..63b8c026ef9c 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -459,6 +459,7 @@ static void flush_unmaps_timeout(unsigned long data); struct deferred_flush_entry { struct iova *iova; + unsigned long nrpages; struct dmar_domain *domain; struct page *freelist; }; @@ -3542,6 +3543,7 @@ static void flush_unmaps(struct deferred_flush_data *flush_data) struct deferred_flush_entry *entry = &flush_table->entries[j]; struct iova *iova = entry->iova; + unsigned long nrpages = entry->nrpages; struct dmar_domain *domain = entry->domain; struct page *freelist = entry->freelist; @@ -3549,10 +3551,9 @@ static void flush_unmaps(struct deferred_flush_data *flush_data) if (cap_caching_mode(iommu->cap)) iommu_flush_iotlb_psi(iommu, domain, mm_to_dma_pfn(iova->pfn_lo), - mm_to_dma_pfn(iova_size(iova)), - !freelist, 0); + nrpages, !freelist, 0); else { - mask = ilog2(mm_to_dma_pfn(iova_size(iova))); + mask = ilog2(nrpages); iommu_flush_dev_iotlb(domain, (uint64_t)iova->pfn_lo << PAGE_SHIFT, mask); } @@ -3576,7 +3577,8 @@ static void flush_unmaps_timeout(unsigned long cpuid) spin_unlock_irqrestore(&flush_data->lock, flags); } -static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *freelist) +static void add_unmap(struct dmar_domain *dom, struct iova *iova, + unsigned long nrpages, struct page *freelist) { unsigned long flags; int entry_id, iommu_id; @@ -3610,6 +3612,7 @@ static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *f entry = &flush_data->tables[iommu_id].entries[entry_id]; entry->domain = dom; entry->iova = iova; + entry->nrpages = nrpages; entry->freelist = freelist; if (!flush_data->timer_on) { @@ -3622,10 +3625,11 @@ static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *f put_cpu(); } -static void intel_unmap(struct device *dev, dma_addr_t dev_addr) +static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size) { struct dmar_domain *domain; unsigned long start_pfn, last_pfn; + unsigned long nrpages; struct iova *iova; struct intel_iommu *iommu; struct page *freelist; @@ -3643,8 +3647,9 @@ static void intel_unmap(struct device *dev, dma_addr_t dev_addr) (unsigned long long)dev_addr)) return; + nrpages = aligned_nrpages(dev_addr, size); start_pfn = mm_to_dma_pfn(iova->pfn_lo); - last_pfn = mm_to_dma_pfn(iova->pfn_hi + 1) - 1; + last_pfn = start_pfn + nrpages - 1; pr_debug("Device %s unmapping: pfn %lx-%lx\n", dev_name(dev), start_pfn, last_pfn); @@ -3653,12 +3658,12 @@ static void intel_unmap(struct device *dev, dma_addr_t dev_addr) if (intel_iommu_strict) { iommu_flush_iotlb_psi(iommu, domain, start_pfn, - last_pfn - start_pfn + 1, !freelist, 0); + nrpages, !freelist, 0); /* free iova */ __free_iova(&domain->iovad, iova); dma_free_pagelist(freelist); } else { - add_unmap(domain, iova, freelist); + add_unmap(domain, iova, nrpages, freelist); /* * queue up the release of the unmap to save the 1/6th of the * cpu used up by the iotlb flush operation... @@ -3670,7 +3675,7 @@ static void intel_unmap_page(struct device *dev, dma_addr_t dev_addr, size_t size, enum dma_data_direction dir, struct dma_attrs *attrs) { - intel_unmap(dev, dev_addr); + intel_unmap(dev, dev_addr, size); } static void *intel_alloc_coherent(struct device *dev, size_t size, @@ -3729,7 +3734,7 @@ static void intel_free_coherent(struct device *dev, size_t size, void *vaddr, size = PAGE_ALIGN(size); order = get_order(size); - intel_unmap(dev, dma_handle); + intel_unmap(dev, dma_handle, size); if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT)) __free_pages(page, order); } @@ -3738,7 +3743,16 @@ static void intel_unmap_sg(struct device *dev, struct scatterlist *sglist, int nelems, enum dma_data_direction dir, struct dma_attrs *attrs) { - intel_unmap(dev, sglist[0].dma_address); + dma_addr_t startaddr = sg_dma_address(sglist) & PAGE_MASK; + unsigned long nrpages = 0; + struct scatterlist *sg; + int i; + + for_each_sg(sglist, sg, nelems, i) { + nrpages += aligned_nrpages(sg_dma_address(sg), sg_dma_len(sg)); + } + + intel_unmap(dev, startaddr, nrpages << VTD_PAGE_SHIFT); } static int intel_nontranslate_map_sg(struct device *hddev, From 0824c5920b16fe11034f3b5d2d48456d282d83f9 Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 19:03:35 +0300 Subject: [PATCH 039/393] iommu/vt-d: avoid dev iotlb logic for domains with no dev iotlbs This patch avoids taking the device_domain_lock in iommu_flush_dev_iotlb() for domains with no dev iotlb devices. Signed-off-by: Omer Peleg [gvdl@google.com: fixed locking issues] Signed-off-by: Godfrey van der Linden [mad@cs.technion.ac.il: rebased and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin Signed-off-by: David Woodhouse --- drivers/iommu/intel-iommu.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index 63b8c026ef9c..d100583e15e3 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -391,6 +391,7 @@ struct dmar_domain { * domain ids are 16 bit wide according * to VT-d spec, section 9.3 */ + bool has_iotlb_device; struct list_head devices; /* all devices' list */ struct iova_domain iovad; /* iova's that belong to this domain */ @@ -1464,10 +1465,35 @@ iommu_support_dev_iotlb (struct dmar_domain *domain, struct intel_iommu *iommu, return NULL; } +static void domain_update_iotlb(struct dmar_domain *domain) +{ + struct device_domain_info *info; + bool has_iotlb_device = false; + + assert_spin_locked(&device_domain_lock); + + list_for_each_entry(info, &domain->devices, link) { + struct pci_dev *pdev; + + if (!info->dev || !dev_is_pci(info->dev)) + continue; + + pdev = to_pci_dev(info->dev); + if (pdev->ats_enabled) { + has_iotlb_device = true; + break; + } + } + + domain->has_iotlb_device = has_iotlb_device; +} + static void iommu_enable_dev_iotlb(struct device_domain_info *info) { struct pci_dev *pdev; + assert_spin_locked(&device_domain_lock); + if (!info || !dev_is_pci(info->dev)) return; @@ -1487,6 +1513,7 @@ static void iommu_enable_dev_iotlb(struct device_domain_info *info) #endif if (info->ats_supported && !pci_enable_ats(pdev, VTD_PAGE_SHIFT)) { info->ats_enabled = 1; + domain_update_iotlb(info->domain); info->ats_qdep = pci_ats_queue_depth(pdev); } } @@ -1495,6 +1522,8 @@ static void iommu_disable_dev_iotlb(struct device_domain_info *info) { struct pci_dev *pdev; + assert_spin_locked(&device_domain_lock); + if (!dev_is_pci(info->dev)) return; @@ -1503,6 +1532,7 @@ static void iommu_disable_dev_iotlb(struct device_domain_info *info) if (info->ats_enabled) { pci_disable_ats(pdev); info->ats_enabled = 0; + domain_update_iotlb(info->domain); } #ifdef CONFIG_INTEL_IOMMU_SVM if (info->pri_enabled) { @@ -1523,6 +1553,9 @@ static void iommu_flush_dev_iotlb(struct dmar_domain *domain, unsigned long flags; struct device_domain_info *info; + if (!domain->has_iotlb_device) + return; + spin_lock_irqsave(&device_domain_lock, flags); list_for_each_entry(info, &domain->devices, link) { if (!info->ats_enabled) @@ -1740,6 +1773,7 @@ static struct dmar_domain *alloc_domain(int flags) memset(domain, 0, sizeof(*domain)); domain->nid = -1; domain->flags = flags; + domain->has_iotlb_device = false; INIT_LIST_HEAD(&domain->devices); return domain; From 2aac630429d986a43ac59525a4cff47a624dc58e Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 11:33:57 +0300 Subject: [PATCH 040/393] iommu/vt-d: change intel-iommu to use IOVA frame numbers Make intel-iommu map/unmap/invalidate work with IOVA pfns instead of pointers to "struct iova". This avoids using the iova struct from the IOVA red-black tree and the resulting explicit find_iova() on unmap. This patch will allow us to cache IOVAs in the next patch, in order to avoid rbtree operations for the majority of map/unmap operations. Note: In eliminating the find_iova() operation, we have also eliminated the sanity check previously done in the unmap flow. Arguably, this was overhead that is better avoided in production code, but it could be brought back as a debug option for driver development. Signed-off-by: Omer Peleg [mad@cs.technion.ac.il: rebased, fixed to not break iova api, and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin Signed-off-by: David Woodhouse --- drivers/iommu/intel-iommu.c | 61 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index d100583e15e3..a8babc43e6d4 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -459,7 +459,7 @@ static LIST_HEAD(dmar_rmrr_units); static void flush_unmaps_timeout(unsigned long data); struct deferred_flush_entry { - struct iova *iova; + unsigned long iova_pfn; unsigned long nrpages; struct dmar_domain *domain; struct page *freelist; @@ -3353,7 +3353,7 @@ static int __init init_dmars(void) } /* This takes a number of _MM_ pages, not VTD pages */ -static struct iova *intel_alloc_iova(struct device *dev, +static unsigned long intel_alloc_iova(struct device *dev, struct dmar_domain *domain, unsigned long nrpages, uint64_t dma_mask) { @@ -3373,16 +3373,16 @@ static struct iova *intel_alloc_iova(struct device *dev, iova = alloc_iova(&domain->iovad, nrpages, IOVA_PFN(DMA_BIT_MASK(32)), 1); if (iova) - return iova; + return iova->pfn_lo; } iova = alloc_iova(&domain->iovad, nrpages, IOVA_PFN(dma_mask), 1); if (unlikely(!iova)) { pr_err("Allocating %ld-page iova for %s failed", nrpages, dev_name(dev)); - return NULL; + return 0; } - return iova; + return iova->pfn_lo; } static struct dmar_domain *__get_valid_domain_for_dev(struct device *dev) @@ -3480,7 +3480,7 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr, { struct dmar_domain *domain; phys_addr_t start_paddr; - struct iova *iova; + unsigned long iova_pfn; int prot = 0; int ret; struct intel_iommu *iommu; @@ -3498,8 +3498,8 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr, iommu = domain_get_iommu(domain); size = aligned_nrpages(paddr, size); - iova = intel_alloc_iova(dev, domain, dma_to_mm_pfn(size), dma_mask); - if (!iova) + iova_pfn = intel_alloc_iova(dev, domain, dma_to_mm_pfn(size), dma_mask); + if (!iova_pfn) goto error; /* @@ -3517,7 +3517,7 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr, * might have two guest_addr mapping to the same host paddr, but this * is not a big problem */ - ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova->pfn_lo), + ret = domain_pfn_mapping(domain, mm_to_dma_pfn(iova_pfn), mm_to_dma_pfn(paddr_pfn), size, prot); if (ret) goto error; @@ -3525,18 +3525,18 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr, /* it's a non-present to present mapping. Only flush if caching mode */ if (cap_caching_mode(iommu->cap)) iommu_flush_iotlb_psi(iommu, domain, - mm_to_dma_pfn(iova->pfn_lo), + mm_to_dma_pfn(iova_pfn), size, 0, 1); else iommu_flush_write_buffer(iommu); - start_paddr = (phys_addr_t)iova->pfn_lo << PAGE_SHIFT; + start_paddr = (phys_addr_t)iova_pfn << PAGE_SHIFT; start_paddr += paddr & ~PAGE_MASK; return start_paddr; error: - if (iova) - __free_iova(&domain->iovad, iova); + if (iova_pfn) + free_iova(&domain->iovad, iova_pfn); pr_err("Device %s request: %zx@%llx dir %d --- failed\n", dev_name(dev), size, (unsigned long long)paddr, dir); return 0; @@ -3576,7 +3576,7 @@ static void flush_unmaps(struct deferred_flush_data *flush_data) unsigned long mask; struct deferred_flush_entry *entry = &flush_table->entries[j]; - struct iova *iova = entry->iova; + unsigned long iova_pfn = entry->iova_pfn; unsigned long nrpages = entry->nrpages; struct dmar_domain *domain = entry->domain; struct page *freelist = entry->freelist; @@ -3584,14 +3584,14 @@ static void flush_unmaps(struct deferred_flush_data *flush_data) /* On real hardware multiple invalidations are expensive */ if (cap_caching_mode(iommu->cap)) iommu_flush_iotlb_psi(iommu, domain, - mm_to_dma_pfn(iova->pfn_lo), + mm_to_dma_pfn(iova_pfn), nrpages, !freelist, 0); else { mask = ilog2(nrpages); iommu_flush_dev_iotlb(domain, - (uint64_t)iova->pfn_lo << PAGE_SHIFT, mask); + (uint64_t)iova_pfn << PAGE_SHIFT, mask); } - __free_iova(&domain->iovad, iova); + free_iova(&domain->iovad, iova_pfn); if (freelist) dma_free_pagelist(freelist); } @@ -3611,7 +3611,7 @@ static void flush_unmaps_timeout(unsigned long cpuid) spin_unlock_irqrestore(&flush_data->lock, flags); } -static void add_unmap(struct dmar_domain *dom, struct iova *iova, +static void add_unmap(struct dmar_domain *dom, unsigned long iova_pfn, unsigned long nrpages, struct page *freelist) { unsigned long flags; @@ -3645,7 +3645,7 @@ static void add_unmap(struct dmar_domain *dom, struct iova *iova, entry = &flush_data->tables[iommu_id].entries[entry_id]; entry->domain = dom; - entry->iova = iova; + entry->iova_pfn = iova_pfn; entry->nrpages = nrpages; entry->freelist = freelist; @@ -3664,7 +3664,7 @@ static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size) struct dmar_domain *domain; unsigned long start_pfn, last_pfn; unsigned long nrpages; - struct iova *iova; + unsigned long iova_pfn; struct intel_iommu *iommu; struct page *freelist; @@ -3676,13 +3676,10 @@ static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size) iommu = domain_get_iommu(domain); - iova = find_iova(&domain->iovad, IOVA_PFN(dev_addr)); - if (WARN_ONCE(!iova, "Driver unmaps unmatched page at PFN %llx\n", - (unsigned long long)dev_addr)) - return; + iova_pfn = IOVA_PFN(dev_addr); nrpages = aligned_nrpages(dev_addr, size); - start_pfn = mm_to_dma_pfn(iova->pfn_lo); + start_pfn = mm_to_dma_pfn(iova_pfn); last_pfn = start_pfn + nrpages - 1; pr_debug("Device %s unmapping: pfn %lx-%lx\n", @@ -3694,10 +3691,10 @@ static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size) iommu_flush_iotlb_psi(iommu, domain, start_pfn, nrpages, !freelist, 0); /* free iova */ - __free_iova(&domain->iovad, iova); + free_iova(&domain->iovad, iova_pfn); dma_free_pagelist(freelist); } else { - add_unmap(domain, iova, nrpages, freelist); + add_unmap(domain, iova_pfn, nrpages, freelist); /* * queue up the release of the unmap to save the 1/6th of the * cpu used up by the iotlb flush operation... @@ -3810,7 +3807,7 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele struct dmar_domain *domain; size_t size = 0; int prot = 0; - struct iova *iova = NULL; + unsigned long iova_pfn; int ret; struct scatterlist *sg; unsigned long start_vpfn; @@ -3829,9 +3826,9 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele for_each_sg(sglist, sg, nelems, i) size += aligned_nrpages(sg->offset, sg->length); - iova = intel_alloc_iova(dev, domain, dma_to_mm_pfn(size), + iova_pfn = intel_alloc_iova(dev, domain, dma_to_mm_pfn(size), *dev->dma_mask); - if (!iova) { + if (!iova_pfn) { sglist->dma_length = 0; return 0; } @@ -3846,13 +3843,13 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL) prot |= DMA_PTE_WRITE; - start_vpfn = mm_to_dma_pfn(iova->pfn_lo); + start_vpfn = mm_to_dma_pfn(iova_pfn); ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot); if (unlikely(ret)) { dma_pte_free_pagetable(domain, start_vpfn, start_vpfn + size - 1); - __free_iova(&domain->iovad, iova); + free_iova(&domain->iovad, iova_pfn); return 0; } From 9257b4a206fc0229dd5f84b78e4d1ebf3f91d270 Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 11:34:11 +0300 Subject: [PATCH 041/393] iommu/iova: introduce per-cpu caching to iova allocation IOVA allocation has two problems that impede high-throughput I/O. First, it can do a linear search over the allocated IOVA ranges. Second, the rbtree spinlock that serializes IOVA allocations becomes contended. Address these problems by creating an API for caching allocated IOVA ranges, so that the IOVA allocator isn't accessed frequently. This patch adds a per-CPU cache, from which CPUs can alloc/free IOVAs without taking the rbtree spinlock. The per-CPU caches are backed by a global cache, to avoid invoking the (linear-time) IOVA allocator without needing to make the per-CPU cache size excessive. This design is based on magazines, as described in "Magazines and Vmem: Extending the Slab Allocator to Many CPUs and Arbitrary Resources" (currently available at https://www.usenix.org/legacy/event/usenix01/bonwick.html) Adding caching on top of the existing rbtree allocator maintains the property that IOVAs are densely packed in the IO virtual address space, which is important for keeping IOMMU page table usage low. To keep the cache size reasonable, we bound the IOVA space a CPU can cache by 32 MiB (we cache a bounded number of IOVA ranges, and only ranges of size <= 128 KiB). The shared global cache is bounded at 4 MiB of IOVA space. Signed-off-by: Omer Peleg [mad@cs.technion.ac.il: rebased, cleaned up and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin [dwmw2: split out VT-d part into a separate patch] Signed-off-by: David Woodhouse --- drivers/iommu/iova.c | 417 ++++++++++++++++++++++++++++++++++++++++--- include/linux/iova.h | 23 ++- 2 files changed, 414 insertions(+), 26 deletions(-) diff --git a/drivers/iommu/iova.c b/drivers/iommu/iova.c index fa0adef32bd6..ba764a0835d3 100644 --- a/drivers/iommu/iova.c +++ b/drivers/iommu/iova.c @@ -20,6 +20,17 @@ #include #include #include +#include +#include + +static bool iova_rcache_insert(struct iova_domain *iovad, + unsigned long pfn, + unsigned long size); +static unsigned long iova_rcache_get(struct iova_domain *iovad, + unsigned long size, + unsigned long limit_pfn); +static void init_iova_rcaches(struct iova_domain *iovad); +static void free_iova_rcaches(struct iova_domain *iovad); void init_iova_domain(struct iova_domain *iovad, unsigned long granule, @@ -38,6 +49,7 @@ init_iova_domain(struct iova_domain *iovad, unsigned long granule, iovad->granule = granule; iovad->start_pfn = start_pfn; iovad->dma_32bit_pfn = pfn_32bit; + init_iova_rcaches(iovad); } EXPORT_SYMBOL_GPL(init_iova_domain); @@ -291,33 +303,18 @@ alloc_iova(struct iova_domain *iovad, unsigned long size, } EXPORT_SYMBOL_GPL(alloc_iova); -/** - * find_iova - find's an iova for a given pfn - * @iovad: - iova domain in question. - * @pfn: - page frame number - * This function finds and returns an iova belonging to the - * given doamin which matches the given pfn. - */ -struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn) +static struct iova * +private_find_iova(struct iova_domain *iovad, unsigned long pfn) { - unsigned long flags; - struct rb_node *node; + struct rb_node *node = iovad->rbroot.rb_node; + + assert_spin_locked(&iovad->iova_rbtree_lock); - /* Take the lock so that no other thread is manipulating the rbtree */ - spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); - node = iovad->rbroot.rb_node; while (node) { struct iova *iova = container_of(node, struct iova, node); /* If pfn falls within iova's range, return iova */ if ((pfn >= iova->pfn_lo) && (pfn <= iova->pfn_hi)) { - spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); - /* We are not holding the lock while this iova - * is referenced by the caller as the same thread - * which called this function also calls __free_iova() - * and it is by design that only one thread can possibly - * reference a particular iova and hence no conflict. - */ return iova; } @@ -327,9 +324,35 @@ struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn) node = node->rb_right; } - spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); return NULL; } + +static void private_free_iova(struct iova_domain *iovad, struct iova *iova) +{ + assert_spin_locked(&iovad->iova_rbtree_lock); + __cached_rbnode_delete_update(iovad, iova); + rb_erase(&iova->node, &iovad->rbroot); + free_iova_mem(iova); +} + +/** + * find_iova - finds an iova for a given pfn + * @iovad: - iova domain in question. + * @pfn: - page frame number + * This function finds and returns an iova belonging to the + * given doamin which matches the given pfn. + */ +struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn) +{ + unsigned long flags; + struct iova *iova; + + /* Take the lock so that no other thread is manipulating the rbtree */ + spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); + iova = private_find_iova(iovad, pfn); + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); + return iova; +} EXPORT_SYMBOL_GPL(find_iova); /** @@ -344,10 +367,8 @@ __free_iova(struct iova_domain *iovad, struct iova *iova) unsigned long flags; spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); - __cached_rbnode_delete_update(iovad, iova); - rb_erase(&iova->node, &iovad->rbroot); + private_free_iova(iovad, iova); spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); - free_iova_mem(iova); } EXPORT_SYMBOL_GPL(__free_iova); @@ -369,6 +390,63 @@ free_iova(struct iova_domain *iovad, unsigned long pfn) } EXPORT_SYMBOL_GPL(free_iova); +/** + * alloc_iova_fast - allocates an iova from rcache + * @iovad: - iova domain in question + * @size: - size of page frames to allocate + * @limit_pfn: - max limit address + * This function tries to satisfy an iova allocation from the rcache, + * and falls back to regular allocation on failure. +*/ +unsigned long +alloc_iova_fast(struct iova_domain *iovad, unsigned long size, + unsigned long limit_pfn) +{ + bool flushed_rcache = false; + unsigned long iova_pfn; + struct iova *new_iova; + + iova_pfn = iova_rcache_get(iovad, size, limit_pfn); + if (iova_pfn) + return iova_pfn; + +retry: + new_iova = alloc_iova(iovad, size, limit_pfn, true); + if (!new_iova) { + unsigned int cpu; + + if (flushed_rcache) + return 0; + + /* Try replenishing IOVAs by flushing rcache. */ + flushed_rcache = true; + for_each_online_cpu(cpu) + free_cpu_cached_iovas(cpu, iovad); + goto retry; + } + + return new_iova->pfn_lo; +} +EXPORT_SYMBOL_GPL(alloc_iova_fast); + +/** + * free_iova_fast - free iova pfn range into rcache + * @iovad: - iova domain in question. + * @pfn: - pfn that is allocated previously + * @size: - # of pages in range + * This functions frees an iova range by trying to put it into the rcache, + * falling back to regular iova deallocation via free_iova() if this fails. + */ +void +free_iova_fast(struct iova_domain *iovad, unsigned long pfn, unsigned long size) +{ + if (iova_rcache_insert(iovad, pfn, size)) + return; + + free_iova(iovad, pfn); +} +EXPORT_SYMBOL_GPL(free_iova_fast); + /** * put_iova_domain - destroys the iova doamin * @iovad: - iova domain in question. @@ -379,6 +457,7 @@ void put_iova_domain(struct iova_domain *iovad) struct rb_node *node; unsigned long flags; + free_iova_rcaches(iovad); spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); node = rb_first(&iovad->rbroot); while (node) { @@ -550,5 +629,295 @@ split_and_remove_iova(struct iova_domain *iovad, struct iova *iova, return NULL; } +/* + * Magazine caches for IOVA ranges. For an introduction to magazines, + * see the USENIX 2001 paper "Magazines and Vmem: Extending the Slab + * Allocator to Many CPUs and Arbitrary Resources" by Bonwick and Adams. + * For simplicity, we use a static magazine size and don't implement the + * dynamic size tuning described in the paper. + */ + +#define IOVA_MAG_SIZE 128 + +struct iova_magazine { + unsigned long size; + unsigned long pfns[IOVA_MAG_SIZE]; +}; + +struct iova_cpu_rcache { + spinlock_t lock; + struct iova_magazine *loaded; + struct iova_magazine *prev; +}; + +static struct iova_magazine *iova_magazine_alloc(gfp_t flags) +{ + return kzalloc(sizeof(struct iova_magazine), flags); +} + +static void iova_magazine_free(struct iova_magazine *mag) +{ + kfree(mag); +} + +static void +iova_magazine_free_pfns(struct iova_magazine *mag, struct iova_domain *iovad) +{ + unsigned long flags; + int i; + + if (!mag) + return; + + spin_lock_irqsave(&iovad->iova_rbtree_lock, flags); + + for (i = 0 ; i < mag->size; ++i) { + struct iova *iova = private_find_iova(iovad, mag->pfns[i]); + + BUG_ON(!iova); + private_free_iova(iovad, iova); + } + + spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags); + + mag->size = 0; +} + +static bool iova_magazine_full(struct iova_magazine *mag) +{ + return (mag && mag->size == IOVA_MAG_SIZE); +} + +static bool iova_magazine_empty(struct iova_magazine *mag) +{ + return (!mag || mag->size == 0); +} + +static unsigned long iova_magazine_pop(struct iova_magazine *mag, + unsigned long limit_pfn) +{ + BUG_ON(iova_magazine_empty(mag)); + + if (mag->pfns[mag->size - 1] >= limit_pfn) + return 0; + + return mag->pfns[--mag->size]; +} + +static void iova_magazine_push(struct iova_magazine *mag, unsigned long pfn) +{ + BUG_ON(iova_magazine_full(mag)); + + mag->pfns[mag->size++] = pfn; +} + +static void init_iova_rcaches(struct iova_domain *iovad) +{ + struct iova_cpu_rcache *cpu_rcache; + struct iova_rcache *rcache; + unsigned int cpu; + int i; + + for (i = 0; i < IOVA_RANGE_CACHE_MAX_SIZE; ++i) { + rcache = &iovad->rcaches[i]; + spin_lock_init(&rcache->lock); + rcache->depot_size = 0; + rcache->cpu_rcaches = __alloc_percpu(sizeof(*cpu_rcache), cache_line_size()); + if (WARN_ON(!rcache->cpu_rcaches)) + continue; + for_each_possible_cpu(cpu) { + cpu_rcache = per_cpu_ptr(rcache->cpu_rcaches, cpu); + spin_lock_init(&cpu_rcache->lock); + cpu_rcache->loaded = iova_magazine_alloc(GFP_KERNEL); + cpu_rcache->prev = iova_magazine_alloc(GFP_KERNEL); + } + } +} + +/* + * Try inserting IOVA range starting with 'iova_pfn' into 'rcache', and + * return true on success. Can fail if rcache is full and we can't free + * space, and free_iova() (our only caller) will then return the IOVA + * range to the rbtree instead. + */ +static bool __iova_rcache_insert(struct iova_domain *iovad, + struct iova_rcache *rcache, + unsigned long iova_pfn) +{ + struct iova_magazine *mag_to_free = NULL; + struct iova_cpu_rcache *cpu_rcache; + bool can_insert = false; + unsigned long flags; + + cpu_rcache = this_cpu_ptr(rcache->cpu_rcaches); + spin_lock_irqsave(&cpu_rcache->lock, flags); + + if (!iova_magazine_full(cpu_rcache->loaded)) { + can_insert = true; + } else if (!iova_magazine_full(cpu_rcache->prev)) { + swap(cpu_rcache->prev, cpu_rcache->loaded); + can_insert = true; + } else { + struct iova_magazine *new_mag = iova_magazine_alloc(GFP_ATOMIC); + + if (new_mag) { + spin_lock(&rcache->lock); + if (rcache->depot_size < MAX_GLOBAL_MAGS) { + rcache->depot[rcache->depot_size++] = + cpu_rcache->loaded; + } else { + mag_to_free = cpu_rcache->loaded; + } + spin_unlock(&rcache->lock); + + cpu_rcache->loaded = new_mag; + can_insert = true; + } + } + + if (can_insert) + iova_magazine_push(cpu_rcache->loaded, iova_pfn); + + spin_unlock_irqrestore(&cpu_rcache->lock, flags); + + if (mag_to_free) { + iova_magazine_free_pfns(mag_to_free, iovad); + iova_magazine_free(mag_to_free); + } + + return can_insert; +} + +static bool iova_rcache_insert(struct iova_domain *iovad, unsigned long pfn, + unsigned long size) +{ + unsigned int log_size = order_base_2(size); + + if (log_size >= IOVA_RANGE_CACHE_MAX_SIZE) + return false; + + return __iova_rcache_insert(iovad, &iovad->rcaches[log_size], pfn); +} + +/* + * Caller wants to allocate a new IOVA range from 'rcache'. If we can + * satisfy the request, return a matching non-NULL range and remove + * it from the 'rcache'. + */ +static unsigned long __iova_rcache_get(struct iova_rcache *rcache, + unsigned long limit_pfn) +{ + struct iova_cpu_rcache *cpu_rcache; + unsigned long iova_pfn = 0; + bool has_pfn = false; + unsigned long flags; + + cpu_rcache = this_cpu_ptr(rcache->cpu_rcaches); + spin_lock_irqsave(&cpu_rcache->lock, flags); + + if (!iova_magazine_empty(cpu_rcache->loaded)) { + has_pfn = true; + } else if (!iova_magazine_empty(cpu_rcache->prev)) { + swap(cpu_rcache->prev, cpu_rcache->loaded); + has_pfn = true; + } else { + spin_lock(&rcache->lock); + if (rcache->depot_size > 0) { + iova_magazine_free(cpu_rcache->loaded); + cpu_rcache->loaded = rcache->depot[--rcache->depot_size]; + has_pfn = true; + } + spin_unlock(&rcache->lock); + } + + if (has_pfn) + iova_pfn = iova_magazine_pop(cpu_rcache->loaded, limit_pfn); + + spin_unlock_irqrestore(&cpu_rcache->lock, flags); + + return iova_pfn; +} + +/* + * Try to satisfy IOVA allocation range from rcache. Fail if requested + * size is too big or the DMA limit we are given isn't satisfied by the + * top element in the magazine. + */ +static unsigned long iova_rcache_get(struct iova_domain *iovad, + unsigned long size, + unsigned long limit_pfn) +{ + unsigned int log_size = order_base_2(size); + + if (log_size >= IOVA_RANGE_CACHE_MAX_SIZE) + return 0; + + return __iova_rcache_get(&iovad->rcaches[log_size], limit_pfn); +} + +/* + * Free a cpu's rcache. + */ +static void free_cpu_iova_rcache(unsigned int cpu, struct iova_domain *iovad, + struct iova_rcache *rcache) +{ + struct iova_cpu_rcache *cpu_rcache = per_cpu_ptr(rcache->cpu_rcaches, cpu); + unsigned long flags; + + spin_lock_irqsave(&cpu_rcache->lock, flags); + + iova_magazine_free_pfns(cpu_rcache->loaded, iovad); + iova_magazine_free(cpu_rcache->loaded); + + iova_magazine_free_pfns(cpu_rcache->prev, iovad); + iova_magazine_free(cpu_rcache->prev); + + spin_unlock_irqrestore(&cpu_rcache->lock, flags); +} + +/* + * free rcache data structures. + */ +static void free_iova_rcaches(struct iova_domain *iovad) +{ + struct iova_rcache *rcache; + unsigned long flags; + unsigned int cpu; + int i, j; + + for (i = 0; i < IOVA_RANGE_CACHE_MAX_SIZE; ++i) { + rcache = &iovad->rcaches[i]; + for_each_possible_cpu(cpu) + free_cpu_iova_rcache(cpu, iovad, rcache); + spin_lock_irqsave(&rcache->lock, flags); + free_percpu(rcache->cpu_rcaches); + for (j = 0; j < rcache->depot_size; ++j) { + iova_magazine_free_pfns(rcache->depot[j], iovad); + iova_magazine_free(rcache->depot[j]); + } + spin_unlock_irqrestore(&rcache->lock, flags); + } +} + +/* + * free all the IOVA ranges cached by a cpu (used when cpu is unplugged) + */ +void free_cpu_cached_iovas(unsigned int cpu, struct iova_domain *iovad) +{ + struct iova_cpu_rcache *cpu_rcache; + struct iova_rcache *rcache; + unsigned long flags; + int i; + + for (i = 0; i < IOVA_RANGE_CACHE_MAX_SIZE; ++i) { + rcache = &iovad->rcaches[i]; + cpu_rcache = per_cpu_ptr(rcache->cpu_rcaches, cpu); + spin_lock_irqsave(&cpu_rcache->lock, flags); + iova_magazine_free_pfns(cpu_rcache->loaded, iovad); + iova_magazine_free_pfns(cpu_rcache->prev, iovad); + spin_unlock_irqrestore(&cpu_rcache->lock, flags); + } +} + MODULE_AUTHOR("Anil S Keshavamurthy "); MODULE_LICENSE("GPL"); diff --git a/include/linux/iova.h b/include/linux/iova.h index 92f7177db2ce..f27bb2c62fca 100644 --- a/include/linux/iova.h +++ b/include/linux/iova.h @@ -19,8 +19,21 @@ /* iova structure */ struct iova { struct rb_node node; - unsigned long pfn_hi; /* IOMMU dish out addr hi */ - unsigned long pfn_lo; /* IOMMU dish out addr lo */ + unsigned long pfn_hi; /* Highest allocated pfn */ + unsigned long pfn_lo; /* Lowest allocated pfn */ +}; + +struct iova_magazine; +struct iova_cpu_rcache; + +#define IOVA_RANGE_CACHE_MAX_SIZE 6 /* log of max cached IOVA range size (in pages) */ +#define MAX_GLOBAL_MAGS 32 /* magazines per bin */ + +struct iova_rcache { + spinlock_t lock; + unsigned long depot_size; + struct iova_magazine *depot[MAX_GLOBAL_MAGS]; + struct iova_cpu_rcache __percpu *cpu_rcaches; }; /* holds all the iova translations for a domain */ @@ -31,6 +44,7 @@ struct iova_domain { unsigned long granule; /* pfn granularity for this domain */ unsigned long start_pfn; /* Lower limit for this domain */ unsigned long dma_32bit_pfn; + struct iova_rcache rcaches[IOVA_RANGE_CACHE_MAX_SIZE]; /* IOVA range caches */ }; static inline unsigned long iova_size(struct iova *iova) @@ -78,6 +92,10 @@ void __free_iova(struct iova_domain *iovad, struct iova *iova); struct iova *alloc_iova(struct iova_domain *iovad, unsigned long size, unsigned long limit_pfn, bool size_aligned); +void free_iova_fast(struct iova_domain *iovad, unsigned long pfn, + unsigned long size); +unsigned long alloc_iova_fast(struct iova_domain *iovad, unsigned long size, + unsigned long limit_pfn); struct iova *reserve_iova(struct iova_domain *iovad, unsigned long pfn_lo, unsigned long pfn_hi); void copy_reserved_iova(struct iova_domain *from, struct iova_domain *to); @@ -87,5 +105,6 @@ struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn); void put_iova_domain(struct iova_domain *iovad); struct iova *split_and_remove_iova(struct iova_domain *iovad, struct iova *iova, unsigned long pfn_lo, unsigned long pfn_hi); +void free_cpu_cached_iovas(unsigned int cpu, struct iova_domain *iovad); #endif From 22e2f9fa63b092923873fc8a52955151f4d83274 Mon Sep 17 00:00:00 2001 From: Omer Peleg Date: Wed, 20 Apr 2016 11:34:11 +0300 Subject: [PATCH 042/393] iommu/vt-d: Use per-cpu IOVA caching Commit 9257b4a2 ('iommu/iova: introduce per-cpu caching to iova allocation') introduced per-CPU IOVA caches to massively improve scalability. Use them. Signed-off-by: Omer Peleg [mad@cs.technion.ac.il: rebased, cleaned up and reworded the commit message] Signed-off-by: Adam Morrison Reviewed-by: Shaohua Li Reviewed-by: Ben Serebrin [dwmw2: split out VT-d part into a separate patch] Signed-off-by: David Woodhouse --- drivers/iommu/intel-iommu.c | 47 +++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index a8babc43e6d4..76e833278db0 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -3357,7 +3357,7 @@ static unsigned long intel_alloc_iova(struct device *dev, struct dmar_domain *domain, unsigned long nrpages, uint64_t dma_mask) { - struct iova *iova = NULL; + unsigned long iova_pfn = 0; /* Restrict dma_mask to the width that the iommu can handle */ dma_mask = min_t(uint64_t, DOMAIN_MAX_ADDR(domain->gaw), dma_mask); @@ -3370,19 +3370,19 @@ static unsigned long intel_alloc_iova(struct device *dev, * DMA_BIT_MASK(32) and if that fails then try allocating * from higher range */ - iova = alloc_iova(&domain->iovad, nrpages, - IOVA_PFN(DMA_BIT_MASK(32)), 1); - if (iova) - return iova->pfn_lo; + iova_pfn = alloc_iova_fast(&domain->iovad, nrpages, + IOVA_PFN(DMA_BIT_MASK(32))); + if (iova_pfn) + return iova_pfn; } - iova = alloc_iova(&domain->iovad, nrpages, IOVA_PFN(dma_mask), 1); - if (unlikely(!iova)) { + iova_pfn = alloc_iova_fast(&domain->iovad, nrpages, IOVA_PFN(dma_mask)); + if (unlikely(!iova_pfn)) { pr_err("Allocating %ld-page iova for %s failed", nrpages, dev_name(dev)); return 0; } - return iova->pfn_lo; + return iova_pfn; } static struct dmar_domain *__get_valid_domain_for_dev(struct device *dev) @@ -3536,7 +3536,7 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr, error: if (iova_pfn) - free_iova(&domain->iovad, iova_pfn); + free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(size)); pr_err("Device %s request: %zx@%llx dir %d --- failed\n", dev_name(dev), size, (unsigned long long)paddr, dir); return 0; @@ -3591,7 +3591,7 @@ static void flush_unmaps(struct deferred_flush_data *flush_data) iommu_flush_dev_iotlb(domain, (uint64_t)iova_pfn << PAGE_SHIFT, mask); } - free_iova(&domain->iovad, iova_pfn); + free_iova_fast(&domain->iovad, iova_pfn, nrpages); if (freelist) dma_free_pagelist(freelist); } @@ -3691,7 +3691,7 @@ static void intel_unmap(struct device *dev, dma_addr_t dev_addr, size_t size) iommu_flush_iotlb_psi(iommu, domain, start_pfn, nrpages, !freelist, 0); /* free iova */ - free_iova(&domain->iovad, iova_pfn); + free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(nrpages)); dma_free_pagelist(freelist); } else { add_unmap(domain, iova_pfn, nrpages, freelist); @@ -3849,7 +3849,7 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele if (unlikely(ret)) { dma_pte_free_pagetable(domain, start_vpfn, start_vpfn + size - 1); - free_iova(&domain->iovad, iova_pfn); + free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(size)); return 0; } @@ -4588,6 +4588,28 @@ static struct notifier_block intel_iommu_memory_nb = { .priority = 0 }; +static void free_all_cpu_cached_iovas(unsigned int cpu) +{ + int i; + + for (i = 0; i < g_num_of_iommus; i++) { + struct intel_iommu *iommu = g_iommus[i]; + struct dmar_domain *domain; + u16 did; + + if (!iommu) + continue; + + for (did = 0; did < 0xffff; did++) { + domain = get_iommu_domain(iommu, did); + + if (!domain) + continue; + free_cpu_cached_iovas(cpu, &domain->iovad); + } + } +} + static int intel_iommu_cpu_notifier(struct notifier_block *nfb, unsigned long action, void *v) { @@ -4596,6 +4618,7 @@ static int intel_iommu_cpu_notifier(struct notifier_block *nfb, switch (action) { case CPU_DEAD: case CPU_DEAD_FROZEN: + free_all_cpu_cached_iovas(cpu); flush_unmaps_timeout(cpu); break; } From 20d2cecbb7b9c35235c97f0dfa520525c28f8841 Mon Sep 17 00:00:00 2001 From: Jeremy McDermond Date: Wed, 20 Apr 2016 11:39:11 -0700 Subject: [PATCH 043/393] ASoC: tlv320aic32x4: Implement resistors on input pins The input pins of the aic3204 have resistors inline with them. The current code assumes that you want a 10k resistor inline with your inputs and implements it as a simple switch. This patch creates an enum for each pin and allows you to switch between not connected, 10k, 20k and 40k ohm values. This more closely models the acutal aic3204 part. These pin settings are documented in TI's SLAA557 pages 135 and 136 (http://www.ti.com/lit/ml/slaa557/slaa557.pdf). Signed-off-by: Jeremy McDermond Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 2f8480c93b3c..621f4210cd27 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -183,16 +183,34 @@ static const struct snd_kcontrol_new lor_output_mixer_controls[] = { SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0), }; -static const struct snd_kcontrol_new left_input_mixer_controls[] = { - SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0), - SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0), - SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0), +static const char * const resistor_text[] = { + "Off", "10 kOhm", "20 kOhm", "40 kOhm", }; +static SOC_ENUM_SINGLE_DECL(in1l_lpga_p_enum, AIC32X4_LMICPGAPIN, 6, + resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_lpga_p_enum, AIC32X4_LMICPGAPIN, 4, + resistor_text); +static SOC_ENUM_SINGLE_DECL(in3l_lpga_p_enum, AIC32X4_LMICPGAPIN, 2, + resistor_text); + +static const struct snd_kcontrol_new left_input_mixer_controls[] = { + SOC_DAPM_ENUM("IN1_L P Switch", in1l_lpga_p_enum), + SOC_DAPM_ENUM("IN2_L P Switch", in2l_lpga_p_enum), + SOC_DAPM_ENUM("IN3_L P Switch", in3l_lpga_p_enum), +}; + +static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, + resistor_text); +static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, + resistor_text); +static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, + resistor_text); + static const struct snd_kcontrol_new right_input_mixer_controls[] = { - SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0), - SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0), - SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0), + SOC_DAPM_ENUM("IN1_R P Switch", in1r_rpga_p_enum), + SOC_DAPM_ENUM("IN2_R P Switch", in2r_rpga_p_enum), + SOC_DAPM_ENUM("IN3_R P Switch", in3r_rpga_p_enum), }; static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { From 13a06ed55dba0ae3f983ef3c4ea70fc42066e1b5 Mon Sep 17 00:00:00 2001 From: Jeremy McDermond Date: Wed, 20 Apr 2016 11:39:12 -0700 Subject: [PATCH 044/393] ASoC: tlv320aic32x4: Add additional input pins The input mixers support routing the IN1_R pin to the Left PGA and the IN2_L pin to the Right PGA. This patch allows for those routings. Signed-off-by: Jeremy McDermond Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 621f4210cd27..0eb8acc8cd66 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -193,11 +193,14 @@ static SOC_ENUM_SINGLE_DECL(in2l_lpga_p_enum, AIC32X4_LMICPGAPIN, 4, resistor_text); static SOC_ENUM_SINGLE_DECL(in3l_lpga_p_enum, AIC32X4_LMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in1r_lpga_p_enum, AIC32X4_LMICPGAPIN, 0, + resistor_text); static const struct snd_kcontrol_new left_input_mixer_controls[] = { SOC_DAPM_ENUM("IN1_L P Switch", in1l_lpga_p_enum), SOC_DAPM_ENUM("IN2_L P Switch", in2l_lpga_p_enum), SOC_DAPM_ENUM("IN3_L P Switch", in3l_lpga_p_enum), + SOC_DAPM_ENUM("IN1_R P Switch", in1r_lpga_p_enum), }; static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, @@ -206,11 +209,14 @@ static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, resistor_text); static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_rpga_p_enum, AIC32X4_RMICPGAPIN, 0, + resistor_text); static const struct snd_kcontrol_new right_input_mixer_controls[] = { SOC_DAPM_ENUM("IN1_R P Switch", in1r_rpga_p_enum), SOC_DAPM_ENUM("IN2_R P Switch", in2r_rpga_p_enum), SOC_DAPM_ENUM("IN3_R P Switch", in3r_rpga_p_enum), + SOC_DAPM_ENUM("IN2_L P Switch", in2l_rpga_p_enum), }; static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { @@ -285,6 +291,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { {"Left Input Mixer", "IN1_L P Switch", "IN1_L"}, {"Left Input Mixer", "IN2_L P Switch", "IN2_L"}, {"Left Input Mixer", "IN3_L P Switch", "IN3_L"}, + {"Left Input Mixer", "IN1_R P Switch", "IN1_R"}, {"Left ADC", NULL, "Left Input Mixer"}, @@ -292,6 +299,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { {"Right Input Mixer", "IN1_R P Switch", "IN1_R"}, {"Right Input Mixer", "IN2_R P Switch", "IN2_R"}, {"Right Input Mixer", "IN3_R P Switch", "IN3_R"}, + {"Right Input Mixer", "IN2_L P Switch", "IN2_L"}, {"Right ADC", NULL, "Right Input Mixer"}, }; From d349caeb05104ef01392abc6c7cfc8ab516c7be4 Mon Sep 17 00:00:00 2001 From: PC Liao Date: Thu, 21 Apr 2016 19:38:14 +0800 Subject: [PATCH 045/393] ASoC: mediatek: Add second I2S on mt8173-rt5650 machine driver This patch adds second I2S connection to rt5650 codec for capture path on mt8173-rt5650 machine driver. Signed-off-by: PC Liao Signed-off-by: Mark Brown --- .../bindings/sound/mt8173-rt5650.txt | 10 ++++ sound/soc/mediatek/mt8173-rt5650.c | 50 +++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/mt8173-rt5650.txt b/Documentation/devicetree/bindings/sound/mt8173-rt5650.txt index fe5a5ef1714d..5bfa6b60530b 100644 --- a/Documentation/devicetree/bindings/sound/mt8173-rt5650.txt +++ b/Documentation/devicetree/bindings/sound/mt8173-rt5650.txt @@ -5,11 +5,21 @@ Required properties: - mediatek,audio-codec: the phandles of rt5650 codecs - mediatek,platform: the phandle of MT8173 ASoC platform +Optional subnodes: +- codec-capture : the subnode of rt5650 codec capture +Required codec-capture subnode properties: +- sound-dai: audio codec dai name on capture path + <&rt5650 0> : Default setting. Connect rt5650 I2S1 for capture. (dai_name = rt5645-aif1) + <&rt5650 1> : Connect rt5650 I2S2 for capture. (dai_name = rt5645-aif2) + Example: sound { compatible = "mediatek,mt8173-rt5650"; mediatek,audio-codec = <&rt5650>; mediatek,platform = <&afe>; + codec-capture { + sound-dai = <&rt5650 1>; + }; }; diff --git a/sound/soc/mediatek/mt8173-rt5650.c b/sound/soc/mediatek/mt8173-rt5650.c index bb09bb1b7f1c..a27a6673dbe3 100644 --- a/sound/soc/mediatek/mt8173-rt5650.c +++ b/sound/soc/mediatek/mt8173-rt5650.c @@ -85,12 +85,29 @@ static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime) { struct snd_soc_card *card = runtime->card; struct snd_soc_codec *codec = runtime->codec_dais[0]->codec; + const char *codec_capture_dai = runtime->codec_dais[1]->name; int ret; rt5645_sel_asrc_clk_src(codec, - RT5645_DA_STEREO_FILTER | - RT5645_AD_STEREO_FILTER, + RT5645_DA_STEREO_FILTER, RT5645_CLK_SEL_I2S1_ASRC); + + if (!strcmp(codec_capture_dai, "rt5645-aif1")) { + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } else if (!strcmp(codec_capture_dai, "rt5645-aif2")) { + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S2_ASRC); + } else { + dev_warn(card->dev, + "Only one dai codec found in DTS, enabled rt5645 AD filter\n"); + rt5645_sel_asrc_clk_src(codec, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } + /* enable jack detection */ ret = snd_soc_card_jack_new(card, "Headset Jack", SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | @@ -110,6 +127,11 @@ static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime) static struct snd_soc_dai_link_component mt8173_rt5650_codecs[] = { { + /* Playback */ + .dai_name = "rt5645-aif1", + }, + { + /* Capture */ .dai_name = "rt5645-aif1", }, }; @@ -149,7 +171,7 @@ static struct snd_soc_dai_link mt8173_rt5650_dais[] = { .cpu_dai_name = "I2S", .no_pcm = 1, .codecs = mt8173_rt5650_codecs, - .num_codecs = 1, + .num_codecs = 2, .init = mt8173_rt5650_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, @@ -177,6 +199,8 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) { struct snd_soc_card *card = &mt8173_rt5650_card; struct device_node *platform_node; + struct device_node *np; + const char *codec_capture_dai; int i, ret; platform_node = of_parse_phandle(pdev->dev.of_node, @@ -199,6 +223,26 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) "Property 'audio-codec' missing or invalid\n"); return -EINVAL; } + mt8173_rt5650_codecs[1].of_node = mt8173_rt5650_codecs[0].of_node; + + if (of_find_node_by_name(platform_node, "codec-capture")) { + np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); + if (!np) { + dev_err(&pdev->dev, + "%s: Can't find codec-capture DT node\n", + __func__); + return -EINVAL; + } + ret = snd_soc_of_get_dai_name(np, &codec_capture_dai); + if (ret < 0) { + dev_err(&pdev->dev, + "%s codec_capture_dai name fail %d\n", + __func__, ret); + return ret; + } + mt8173_rt5650_codecs[1].dai_name = codec_capture_dai; + } + card->dev = &pdev->dev; platform_set_drvdata(pdev, card); From c0133e3b0265341e7d62e150df18709af33c3a30 Mon Sep 17 00:00:00 2001 From: Koro Chen Date: Wed, 20 Apr 2016 10:59:56 +0200 Subject: [PATCH 046/393] ASoC: mediatek: Add HDMI dai-links in the mt8173-rt5650-rt5676 machine driver This creates pcmC0D2p for the HDMI playback in the same card. Signed-off-by: Koro Chen Signed-off-by: Philipp Zabel Signed-off-by: Mark Brown --- .../bindings/sound/mt8173-rt5650-rt5676.txt | 5 ++-- sound/soc/mediatek/Kconfig | 1 + sound/soc/mediatek/mt8173-rt5650-rt5676.c | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5676.txt b/Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5676.txt index f205ce9e31dd..ac28cdb4910e 100644 --- a/Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5676.txt +++ b/Documentation/devicetree/bindings/sound/mt8173-rt5650-rt5676.txt @@ -1,15 +1,16 @@ -MT8173 with RT5650 RT5676 CODECS +MT8173 with RT5650 RT5676 CODECS and HDMI via I2S Required properties: - compatible : "mediatek,mt8173-rt5650-rt5676" - mediatek,audio-codec: the phandles of rt5650 and rt5676 codecs + and of the hdmi encoder node - mediatek,platform: the phandle of MT8173 ASoC platform Example: sound { compatible = "mediatek,mt8173-rt5650-rt5676"; - mediatek,audio-codec = <&rt5650 &rt5676>; + mediatek,audio-codec = <&rt5650 &rt5676 &hdmi0>; mediatek,platform = <&afe>; }; diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index f7e789e97fbc..3abf51c07851 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -43,6 +43,7 @@ config SND_SOC_MT8173_RT5650_RT5676 depends on SND_SOC_MEDIATEK && I2C select SND_SOC_RT5645 select SND_SOC_RT5677 + select SND_SOC_HDMI_CODEC help This adds ASoC driver for Mediatek MT8173 boards with the RT5650 and RT5676 codecs. diff --git a/sound/soc/mediatek/mt8173-rt5650-rt5676.c b/sound/soc/mediatek/mt8173-rt5650-rt5676.c index 5c4c58c69c51..bb593926c62d 100644 --- a/sound/soc/mediatek/mt8173-rt5650-rt5676.c +++ b/sound/soc/mediatek/mt8173-rt5650-rt5676.c @@ -134,7 +134,9 @@ static struct snd_soc_dai_link_component mt8173_rt5650_rt5676_codecs[] = { enum { DAI_LINK_PLAYBACK, DAI_LINK_CAPTURE, + DAI_LINK_HDMI, DAI_LINK_CODEC_I2S, + DAI_LINK_HDMI_I2S, DAI_LINK_INTERCODEC }; @@ -161,6 +163,16 @@ static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = { .dynamic = 1, .dpcm_capture = 1, }, + [DAI_LINK_HDMI] = { + .name = "HDMI", + .stream_name = "HDMI PCM", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, /* Back End DAI links */ [DAI_LINK_CODEC_I2S] = { @@ -177,6 +189,13 @@ static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = { .dpcm_playback = 1, .dpcm_capture = 1, }, + [DAI_LINK_HDMI_I2S] = { + .name = "HDMI BE", + .cpu_dai_name = "HDMIO", + .no_pcm = 1, + .codec_dai_name = "i2s-hifi", + .dpcm_playback = 1, + }, /* rt5676 <-> rt5650 intercodec link: Sets rt5676 I2S2 as master */ [DAI_LINK_INTERCODEC] = { .name = "rt5650_rt5676 intercodec", @@ -251,6 +270,14 @@ static int mt8173_rt5650_rt5676_dev_probe(struct platform_device *pdev) mt8173_rt5650_rt5676_dais[DAI_LINK_INTERCODEC].codec_of_node = mt8173_rt5650_rt5676_codecs[1].of_node; + mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 2); + if (!mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + card->dev = &pdev->dev; platform_set_drvdata(pdev, card); From 54aba08f13fab00f31938993c9f7a9b0cd54f666 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 22 Apr 2016 09:09:14 +0000 Subject: [PATCH 047/393] ASoC: tidyup alphabetical order for SND_SOC_Bxx Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 8 ++++---- sound/soc/codecs/Makefile | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 649e92a252ae..f7f64bd019a5 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -43,6 +43,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK5386 select SND_SOC_ALC5623 if I2C select SND_SOC_ALC5632 if I2C + select SND_SOC_BT_SCO select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS35L32 if I2C select SND_SOC_CS42L51_I2C if I2C @@ -64,7 +65,6 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA732X if I2C select SND_SOC_DA9055 if I2C select SND_SOC_DMIC - select SND_SOC_BT_SCO select SND_SOC_ES8328_SPI if SPI_MASTER select SND_SOC_ES8328_I2C if I2C select SND_SOC_GTM601 @@ -365,6 +365,9 @@ config SND_SOC_ALC5623 config SND_SOC_ALC5632 tristate +config SND_SOC_BT_SCO + tristate + config SND_SOC_CQ0093VC tristate @@ -471,9 +474,6 @@ config SND_SOC_DA732X config SND_SOC_DA9055 tristate -config SND_SOC_BT_SCO - tristate - config SND_SOC_DMIC tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 185a712a7fe7..12a1fe22eaa1 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -32,6 +32,7 @@ snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-ak5386-objs := ak5386.o snd-soc-arizona-objs := arizona.o +snd-soc-bt-sco-objs := bt-sco.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o snd-soc-cs42l51-objs := cs42l51.o @@ -55,7 +56,6 @@ snd-soc-da7218-objs := da7218.o snd-soc-da7219-objs := da7219.o da7219-aad.o snd-soc-da732x-objs := da732x.o snd-soc-da9055-objs := da9055.o -snd-soc-bt-sco-objs := bt-sco.o snd-soc-dmic-objs := dmic.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o @@ -241,6 +241,7 @@ obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o +obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o @@ -264,7 +265,6 @@ obj-$(CONFIG_SND_SOC_DA7218) += snd-soc-da7218.o obj-$(CONFIG_SND_SOC_DA7219) += snd-soc-da7219.o obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o -obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o From 198b618ab118c7d856278a985de1ed0eff77c02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Sat, 16 Apr 2016 03:27:12 +0300 Subject: [PATCH 048/393] asus-laptop: correct error handling in asus_read_brightness() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is possible that acpi_evaluate_integer might fail and value would not be set to any value so correct this defect by returning 0 in case of an error. This is also the correct thing to return because the backlight subsystem will print the old value of brightness in this case. Signed-off-by: Giedrius Statkevičius Signed-off-by: Darren Hart --- drivers/platform/x86/asus-laptop.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index f2b5d0a8adf0..a4cd78691ac9 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -775,8 +775,10 @@ static int asus_read_brightness(struct backlight_device *bd) rv = acpi_evaluate_integer(asus->handle, METHOD_BRIGHTNESS_GET, NULL, &value); - if (ACPI_FAILURE(rv)) + if (ACPI_FAILURE(rv)) { pr_warn("Error reading brightness\n"); + return 0; + } return value; } From 2ce6d9932db55cfc09dd6362d6d0d47a361f5f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Sat, 16 Apr 2016 03:01:56 +0300 Subject: [PATCH 049/393] asus-laptop: remove redundant initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initializing rv to AE_OK is pointless because later function results are assigned to them and only then the variable is used Signed-off-by: Giedrius Statkevičius Acked-by: Andy Shevchenko Signed-off-by: Darren Hart --- drivers/platform/x86/asus-laptop.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index a4cd78691ac9..223090c9d433 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -771,7 +771,7 @@ static int asus_read_brightness(struct backlight_device *bd) { struct asus_laptop *asus = bl_get_data(bd); unsigned long long value; - acpi_status rv = AE_OK; + acpi_status rv; rv = acpi_evaluate_integer(asus->handle, METHOD_BRIGHTNESS_GET, NULL, &value); @@ -867,7 +867,7 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr, int len = 0; unsigned long long temp; char buf[16]; /* enough for all info */ - acpi_status rv = AE_OK; + acpi_status rv; /* * We use the easy way, we don't care of off and count, @@ -1267,7 +1267,7 @@ static DEVICE_ATTR_RO(ls_value); static int asus_gps_status(struct asus_laptop *asus) { unsigned long long status; - acpi_status rv = AE_OK; + acpi_status rv; rv = acpi_evaluate_integer(asus->handle, METHOD_GPS_STATUS, NULL, &status); From 19d46ee1aec06de3dc1137c76c29e07bfd96d99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Sat, 16 Apr 2016 03:01:57 +0300 Subject: [PATCH 050/393] asus-laptop: correct error handling in sysfs_acpi_set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Properly return rv back to the caller in the case of an error in parse_arg. In the process remove a unused variable 'out'. Signed-off-by: Giedrius Statkevičius Signed-off-by: Darren Hart --- drivers/platform/x86/asus-laptop.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 223090c9d433..15f131146501 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -948,11 +948,10 @@ static ssize_t sysfs_acpi_set(struct asus_laptop *asus, const char *method) { int rv, value; - int out = 0; rv = parse_arg(buf, count, &value); - if (rv > 0) - out = value ? 1 : 0; + if (rv <= 0) + return rv; if (write_acpi_int(asus->handle, method, value)) return -ENODEV; From 3c9e014c442caefa14c71494ca4473121007f60f Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 26 Apr 2016 18:07:10 +0800 Subject: [PATCH 051/393] ASoC: rt298: reset AD dilter is there is no MCLK rt298 need to reset AD filter and the ADC settings will take effort. Signed-off-by: Bard Liao Signed-off-by: Mark Brown --- sound/soc/codecs/rt298.c | 20 ++++++++++++++++++++ sound/soc/codecs/rt298.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/sound/soc/codecs/rt298.c b/sound/soc/codecs/rt298.c index f0e6c06e89ac..178b1cc22e05 100644 --- a/sound/soc/codecs/rt298.c +++ b/sound/soc/codecs/rt298.c @@ -481,6 +481,26 @@ static int rt298_adc_event(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), 0x7080, 0x7000); + /* If MCLK doesn't exist, reset AD filter */ + if (!(snd_soc_read(codec, RT298_VAD_CTRL) & 0x200)) { + pr_info("NO MCLK\n"); + switch (nid) { + case RT298_ADC_IN1: + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x2, 0x2); + mdelay(10); + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x2, 0x0); + break; + case RT298_ADC_IN2: + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x4, 0x4); + mdelay(10); + snd_soc_update_bits(codec, + RT298_D_FILTER_CTRL, 0x4, 0x0); + break; + } + } break; case SND_SOC_DAPM_PRE_PMD: snd_soc_update_bits(codec, diff --git a/sound/soc/codecs/rt298.h b/sound/soc/codecs/rt298.h index d66f8847b676..3638f3d61209 100644 --- a/sound/soc/codecs/rt298.h +++ b/sound/soc/codecs/rt298.h @@ -137,6 +137,7 @@ #define RT298_A_BIAS_CTRL2 0x02 #define RT298_POWER_CTRL1 0x03 #define RT298_A_BIAS_CTRL3 0x04 +#define RT298_D_FILTER_CTRL 0x05 #define RT298_POWER_CTRL2 0x08 #define RT298_I2S_CTRL1 0x09 #define RT298_I2S_CTRL2 0x0a @@ -148,6 +149,7 @@ #define RT298_IRQ_CTRL 0x33 #define RT298_WIND_FILTER_CTRL 0x46 #define RT298_PLL_CTRL1 0x49 +#define RT298_VAD_CTRL 0x4e #define RT298_CBJ_CTRL1 0x4f #define RT298_CBJ_CTRL2 0x50 #define RT298_PLL_CTRL 0x63 From 9ff49ce475cfeaf486321a2db8132a9500740faa Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 26 Apr 2016 18:07:11 +0800 Subject: [PATCH 052/393] ASoC: rt298: fix capture doesn't work at some cases RT298_CBJ_CTRL1(0x4f) bit 10 is needed for headset capture. It will be turned off when "VREF" widget is on and be turned on when bias level is ON. It is odd. And if "VREF" is turned on in bias level is ON, RT298_CBJ_CTRL1(0x4f) bit 10 will be turned off. This patch move the bit control from rt298_set_bias_level and rt298_vref_event to rt298_jack_detect. So it will be turned on once a jack is plugged in. Signed-off-by: Bard Liao Signed-off-by: Mark Brown --- sound/soc/codecs/rt298.c | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/sound/soc/codecs/rt298.c b/sound/soc/codecs/rt298.c index 178b1cc22e05..68cf8d5a174f 100644 --- a/sound/soc/codecs/rt298.c +++ b/sound/soc/codecs/rt298.c @@ -275,6 +275,8 @@ static int rt298_jack_detect(struct rt298_priv *rt298, bool *hp, bool *mic) } else { *mic = false; regmap_write(rt298->regmap, RT298_SET_MIC1, 0x20); + regmap_update_bits(rt298->regmap, + RT298_CBJ_CTRL1, 0x0400, 0x0000); } } else { regmap_read(rt298->regmap, RT298_GET_HP_SENSE, &buf); @@ -539,30 +541,12 @@ static int rt298_mic1_event(struct snd_soc_dapm_widget *w, return 0; } -static int rt298_vref_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); - - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - snd_soc_update_bits(codec, - RT298_CBJ_CTRL1, 0x0400, 0x0000); - mdelay(50); - break; - default: - return 0; - } - - return 0; -} - static const struct snd_soc_dapm_widget rt298_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY_S("HV", 1, RT298_POWER_CTRL1, 12, 1, NULL, 0), SND_SOC_DAPM_SUPPLY("VREF", RT298_POWER_CTRL1, - 0, 1, rt298_vref_event, SND_SOC_DAPM_PRE_PMU), + 0, 1, NULL, 0), SND_SOC_DAPM_SUPPLY_S("BG_MBIAS", 1, RT298_POWER_CTRL2, 1, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("LDO1", 1, RT298_POWER_CTRL2, @@ -953,18 +937,9 @@ static int rt298_set_bias_level(struct snd_soc_codec *codec, } break; - case SND_SOC_BIAS_ON: - mdelay(30); - snd_soc_update_bits(codec, - RT298_CBJ_CTRL1, 0x0400, 0x0400); - - break; - case SND_SOC_BIAS_STANDBY: snd_soc_write(codec, RT298_SET_AUDIO_POWER, AC_PWRST_D3); - snd_soc_update_bits(codec, - RT298_CBJ_CTRL1, 0x0400, 0x0000); break; default: From 27becea06e73c96b825ecddd8b3a59642364514a Mon Sep 17 00:00:00 2001 From: PC Liao Date: Tue, 26 Apr 2016 14:30:18 +0800 Subject: [PATCH 053/393] ASoC: mediatek: HDMI audio LR channel swapped Because LRCK of TDM use High to Low as default setting, this patch changes the TDM setting to inverse LRCK. Signed-off-by: PC Liao Signed-off-by: Mark Brown --- sound/soc/mediatek/mtk-afe-pcm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/mediatek/mtk-afe-pcm.c b/sound/soc/mediatek/mtk-afe-pcm.c index f1c58a2c12fb..2b5df2ef51a3 100644 --- a/sound/soc/mediatek/mtk-afe-pcm.c +++ b/sound/soc/mediatek/mtk-afe-pcm.c @@ -123,6 +123,7 @@ #define AFE_TDM_CON1_WLEN_32BIT (0x2 << 8) #define AFE_TDM_CON1_MSB_ALIGNED (0x1 << 4) #define AFE_TDM_CON1_1_BCK_DELAY (0x1 << 3) +#define AFE_TDM_CON1_LRCK_INV (0x1 << 2) #define AFE_TDM_CON1_BCK_INV (0x1 << 1) #define AFE_TDM_CON1_EN (0x1 << 0) @@ -449,6 +450,7 @@ static int mtk_afe_hdmi_prepare(struct snd_pcm_substream *substream, runtime->rate * runtime->channels * 32); val = AFE_TDM_CON1_BCK_INV | + AFE_TDM_CON1_LRCK_INV | AFE_TDM_CON1_1_BCK_DELAY | AFE_TDM_CON1_MSB_ALIGNED | /* I2S mode */ AFE_TDM_CON1_WLEN_32BIT | From 989ff7754a27a1fcc0c3b2794c985b31811871a0 Mon Sep 17 00:00:00 2001 From: Jim Lodes Date: Mon, 25 Apr 2016 11:10:07 -0500 Subject: [PATCH 054/393] ASoC: omap-pcm: Initialize DMA configuration Initialize the dma_slave_config for PCM DMA transfers, instead of leaving it uninitialized. Keeps previous data on the stack from giving us invalid values in uninitialized members of the config structure. Signed-off-by: Jim Lodes Signed-off-by: J.D. Schroeder Acked-by: Jarkko Nikula Reviewed-by: Jarkko Nikula Signed-off-by: Mark Brown --- sound/soc/omap/omap-pcm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 6bb623a2a4df..eb81b9a2293a 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -82,6 +82,8 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, struct dma_chan *chan; int err = 0; + memset(&config, 0x00, sizeof(config)); + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); /* return if this is a bufferless transfer e.g. From b7bde4178a61a6855e1afebb60db4358dbbb6830 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 02:15:15 +0200 Subject: [PATCH 055/393] btrfs: rename and document compression workspace members The names are confusing, pick more fitting names and add comments. Signed-off-by: David Sterba --- fs/btrfs/compression.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index ff61a41ac90b..4d5cd9624bb3 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -743,8 +743,11 @@ int btrfs_submit_compressed_read(struct inode *inode, struct bio *bio, static struct { struct list_head idle_ws; spinlock_t ws_lock; - int num_ws; - atomic_t alloc_ws; + /* Number of free workspaces */ + int free_ws; + /* Total number of allocated workspaces */ + atomic_t total_ws; + /* Waiters for a free workspace */ wait_queue_head_t ws_wait; } btrfs_comp_ws[BTRFS_COMPRESS_TYPES]; @@ -760,7 +763,7 @@ void __init btrfs_init_compress(void) for (i = 0; i < BTRFS_COMPRESS_TYPES; i++) { INIT_LIST_HEAD(&btrfs_comp_ws[i].idle_ws); spin_lock_init(&btrfs_comp_ws[i].ws_lock); - atomic_set(&btrfs_comp_ws[i].alloc_ws, 0); + atomic_set(&btrfs_comp_ws[i].total_ws, 0); init_waitqueue_head(&btrfs_comp_ws[i].ws_wait); } } @@ -777,35 +780,35 @@ static struct list_head *find_workspace(int type) struct list_head *idle_ws = &btrfs_comp_ws[idx].idle_ws; spinlock_t *ws_lock = &btrfs_comp_ws[idx].ws_lock; - atomic_t *alloc_ws = &btrfs_comp_ws[idx].alloc_ws; + atomic_t *total_ws = &btrfs_comp_ws[idx].total_ws; wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; - int *num_ws = &btrfs_comp_ws[idx].num_ws; + int *free_ws = &btrfs_comp_ws[idx].free_ws; again: spin_lock(ws_lock); if (!list_empty(idle_ws)) { workspace = idle_ws->next; list_del(workspace); - (*num_ws)--; + (*free_ws)--; spin_unlock(ws_lock); return workspace; } - if (atomic_read(alloc_ws) > cpus) { + if (atomic_read(total_ws) > cpus) { DEFINE_WAIT(wait); spin_unlock(ws_lock); prepare_to_wait(ws_wait, &wait, TASK_UNINTERRUPTIBLE); - if (atomic_read(alloc_ws) > cpus && !*num_ws) + if (atomic_read(total_ws) > cpus && !*free_ws) schedule(); finish_wait(ws_wait, &wait); goto again; } - atomic_inc(alloc_ws); + atomic_inc(total_ws); spin_unlock(ws_lock); workspace = btrfs_compress_op[idx]->alloc_workspace(); if (IS_ERR(workspace)) { - atomic_dec(alloc_ws); + atomic_dec(total_ws); wake_up(ws_wait); } return workspace; @@ -820,21 +823,21 @@ static void free_workspace(int type, struct list_head *workspace) int idx = type - 1; struct list_head *idle_ws = &btrfs_comp_ws[idx].idle_ws; spinlock_t *ws_lock = &btrfs_comp_ws[idx].ws_lock; - atomic_t *alloc_ws = &btrfs_comp_ws[idx].alloc_ws; + atomic_t *total_ws = &btrfs_comp_ws[idx].total_ws; wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; - int *num_ws = &btrfs_comp_ws[idx].num_ws; + int *free_ws = &btrfs_comp_ws[idx].free_ws; spin_lock(ws_lock); - if (*num_ws < num_online_cpus()) { + if (*free_ws < num_online_cpus()) { list_add(workspace, idle_ws); - (*num_ws)++; + (*free_ws)++; spin_unlock(ws_lock); goto wake; } spin_unlock(ws_lock); btrfs_compress_op[idx]->free_workspace(workspace); - atomic_dec(alloc_ws); + atomic_dec(total_ws); wake: /* * Make sure counter is updated before we wake up waiters. @@ -857,7 +860,7 @@ static void free_workspaces(void) workspace = btrfs_comp_ws[i].idle_ws.next; list_del(workspace); btrfs_compress_op[i]->free_workspace(workspace); - atomic_dec(&btrfs_comp_ws[i].alloc_ws); + atomic_dec(&btrfs_comp_ws[i].total_ws); } } } From 3853368b95f27d9bdaf9e15bc1e11753f11e3c43 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 02:55:15 +0200 Subject: [PATCH 056/393] btrfs: preallocate compression workspaces Preallocate one workspace for each compression type so we can guarantee forward progress in the worst case. A failure cannot be a hard error as we might not use compression at all on the filesystem. If we can't allocate the workspaces later when need them, it might actually deadlock, but in such situation the system has effectively not enough memory to operate properly. Signed-off-by: David Sterba --- fs/btrfs/compression.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 4d5cd9624bb3..38c058bcf359 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -761,10 +761,26 @@ void __init btrfs_init_compress(void) int i; for (i = 0; i < BTRFS_COMPRESS_TYPES; i++) { + struct list_head *workspace; + INIT_LIST_HEAD(&btrfs_comp_ws[i].idle_ws); spin_lock_init(&btrfs_comp_ws[i].ws_lock); atomic_set(&btrfs_comp_ws[i].total_ws, 0); init_waitqueue_head(&btrfs_comp_ws[i].ws_wait); + + /* + * Preallocate one workspace for each compression type so + * we can guarantee forward progress in the worst case + */ + workspace = btrfs_compress_op[i]->alloc_workspace(); + if (IS_ERR(workspace)) { + printk(KERN_WARNING + "BTRFS: cannot preallocate compression workspace, will try later"); + } else { + atomic_set(&btrfs_comp_ws[i].total_ws, 1); + btrfs_comp_ws[i].free_ws = 1; + list_add(workspace, &btrfs_comp_ws[i].idle_ws); + } } } From 3b501d18aa4f75fe23af5b0bf592d62fd82cca5b Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 02:41:17 +0200 Subject: [PATCH 057/393] btrfs: make find_workspace always succeed With just one preallocated workspace we can guarantee forward progress even if there's no memory available for new workspaces. The cost is more waiting but we also get rid of several error paths. On average, there will be several idle workspaces, so the waiting penalty won't be so bad. In the worst case, all cpus will compete for one workspace until there's some memory. Attempts to allocate a new one are done each time the waiters are woken up. Signed-off-by: David Sterba --- fs/btrfs/compression.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 38c058bcf359..c70625560265 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -785,8 +785,10 @@ void __init btrfs_init_compress(void) } /* - * this finds an available workspace or allocates a new one - * ERR_PTR is returned if things go bad. + * This finds an available workspace or allocates a new one. + * If it's not possible to allocate a new one, waits until there's one. + * Preallocation makes a forward progress guarantees and we do not return + * errors. */ static struct list_head *find_workspace(int type) { @@ -826,6 +828,14 @@ static struct list_head *find_workspace(int type) if (IS_ERR(workspace)) { atomic_dec(total_ws); wake_up(ws_wait); + + /* + * Do not return the error but go back to waiting. There's a + * workspace preallocated for each type and the compression + * time is bounded so we get to a workspace eventually. This + * makes our caller's life easier. + */ + goto again; } return workspace; } @@ -913,8 +923,6 @@ int btrfs_compress_pages(int type, struct address_space *mapping, int ret; workspace = find_workspace(type); - if (IS_ERR(workspace)) - return PTR_ERR(workspace); ret = btrfs_compress_op[type-1]->compress_pages(workspace, mapping, start, len, pages, @@ -949,8 +957,6 @@ static int btrfs_decompress_biovec(int type, struct page **pages_in, int ret; workspace = find_workspace(type); - if (IS_ERR(workspace)) - return PTR_ERR(workspace); ret = btrfs_compress_op[type-1]->decompress_biovec(workspace, pages_in, disk_start, @@ -971,8 +977,6 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page, int ret; workspace = find_workspace(type); - if (IS_ERR(workspace)) - return PTR_ERR(workspace); ret = btrfs_compress_op[type-1]->decompress(workspace, data_in, dest_page, start_byte, From ae55b9ec7ac117c05ff866500425fe8b4accfebe Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 03:07:39 +0200 Subject: [PATCH 058/393] btrfs: make find_workspace warn if there are no workspaces Be verbose if there are no workspaces at all, ie. the module init time preallocation failed. Signed-off-by: David Sterba --- fs/btrfs/compression.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index c70625560265..658c39b70fba 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -834,7 +834,21 @@ static struct list_head *find_workspace(int type) * workspace preallocated for each type and the compression * time is bounded so we get to a workspace eventually. This * makes our caller's life easier. + * + * To prevent silent and low-probability deadlocks (when the + * initial preallocation fails), check if there are any + * workspaces at all. */ + if (atomic_read(total_ws) == 0) { + static DEFINE_RATELIMIT_STATE(_rs, + /* once per minute */ 60 * HZ, + /* no burst */ 1); + + if (__ratelimit(&_rs)) { + printk(KERN_WARNING + "no compression workspaces, low memory, retrying"); + } + } goto again; } return workspace; From 4e2f17be8f63a6ad9ebd4cdfce627e3dd25d80bb Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Tue, 26 Apr 2016 17:15:56 -0500 Subject: [PATCH 059/393] ASoC: add TA5720 digital amplifier DT bindings The Texas Instruments TAS5720L/M device is a high-efficiency mono Class-D audio power amplifier optimized for high transient power capability to use the dynamic power headroom of small loudspeakers. Its digital time division multiplexed (TDM) interface enables up to 16 devices to share the same bus. Signed-off-by: Andreas Dannenberg Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/tas5720.txt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/tas5720.txt diff --git a/Documentation/devicetree/bindings/sound/tas5720.txt b/Documentation/devicetree/bindings/sound/tas5720.txt new file mode 100644 index 000000000000..806ea7381483 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/tas5720.txt @@ -0,0 +1,25 @@ +Texas Instruments TAS5720 Mono Audio amplifier + +The TAS5720 serial control bus communicates through the I2C protocol only. The +serial bus is also used for periodic codec fault checking/reporting during +audio playback. For more product information please see the links below: + +http://www.ti.com/product/TAS5720L +http://www.ti.com/product/TAS5720M + +Required properties: + +- compatible : "ti,tas5720" +- reg : I2C slave address +- dvdd-supply : phandle to a 3.3-V supply for the digital circuitry +- pvdd-supply : phandle to a supply used for the Class-D amp and the analog + +Example: + +tas5720: tas5720@6c { + status = "okay"; + compatible = "ti,tas5720"; + reg = <0x6c>; + dvdd-supply = <&vdd_3v3_reg>; + pvdd-supply = <&_supply_reg>; +}; From bd023ada36a66fe3d2fde619926b49b9a0428133 Mon Sep 17 00:00:00 2001 From: Andreas Dannenberg Date: Tue, 26 Apr 2016 17:15:57 -0500 Subject: [PATCH 060/393] ASoC: add support for TAS5720 digital amplifier The Texas Instruments TAS5720L/M device is a high-efficiency mono Class-D audio power amplifier optimized for high transient power capability to use the dynamic power headroom of small loudspeakers. Its digital time division multiplexed (TDM) interface enables up to 16 devices to share the same bus. Signed-off-by: Andreas Dannenberg Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 8 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tas5720.c | 620 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tas5720.h | 90 ++++++ 4 files changed, 720 insertions(+) create mode 100644 sound/soc/codecs/tas5720.c create mode 100644 sound/soc/codecs/tas5720.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c011f076d58b..06d298b79b9b 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -124,6 +124,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TAS2552 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C + select SND_SOC_TAS5720 if I2C select SND_SOC_TFA9879 if I2C select SND_SOC_TLV320AIC23_I2C if I2C select SND_SOC_TLV320AIC23_SPI if SPI_MASTER @@ -740,6 +741,13 @@ config SND_SOC_TAS571X tristate "Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 power amplifiers" depends on I2C +config SND_SOC_TAS5720 + tristate "Texas Instruments TAS5720 Mono Audio amplifier" + depends on I2C + help + Enable support for Texas Instruments TAS5720L/M high-efficiency mono + Class-D audio power amplifiers. + config SND_SOC_TFA9879 tristate "NXP Semiconductors TFA9879 amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 185a712a7fe7..83d352ede6fd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -129,6 +129,7 @@ snd-soc-stac9766-objs := stac9766.o snd-soc-sti-sas-objs := sti-sas.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o +snd-soc-tas5720-objs := tas5720.o snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o @@ -335,6 +336,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o +obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o diff --git a/sound/soc/codecs/tas5720.c b/sound/soc/codecs/tas5720.c new file mode 100644 index 000000000000..f54fb46b77c2 --- /dev/null +++ b/sound/soc/codecs/tas5720.c @@ -0,0 +1,620 @@ +/* + * tas5720.c - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier + * + * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tas5720.h" + +/* Define how often to check (and clear) the fault status register (in ms) */ +#define TAS5720_FAULT_CHECK_INTERVAL 200 + +static const char * const tas5720_supply_names[] = { + "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ + "pvdd", /* Class-D amp and analog power supply (connected). */ +}; + +#define TAS5720_NUM_SUPPLIES ARRAY_SIZE(tas5720_supply_names) + +struct tas5720_data { + struct snd_soc_codec *codec; + struct regmap *regmap; + struct i2c_client *tas5720_client; + struct regulator_bulk_data supplies[TAS5720_NUM_SUPPLIES]; + struct delayed_work fault_check_work; + unsigned int last_fault; +}; + +static int tas5720_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int rate = params_rate(params); + bool ssz_ds; + int ret; + + switch (rate) { + case 44100: + case 48000: + ssz_ds = false; + break; + case 88200: + case 96000: + ssz_ds = true; + break; + default: + dev_err(codec->dev, "unsupported sample rate: %u\n", rate); + return -EINVAL; + } + + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_SSZ_DS, ssz_ds); + if (ret < 0) { + dev_err(codec->dev, "error setting sample rate: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u8 serial_format; + int ret; + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_vdbg(codec->dev, "DAI Format master is not found\n"); + return -EINVAL; + } + + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_INV_MASK)) { + case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): + /* 1st data bit occur one BCLK cycle after the frame sync */ + serial_format = TAS5720_SAIF_I2S; + break; + case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF): + /* + * Note that although the TAS5720 does not have a dedicated DSP + * mode it doesn't care about the LRCLK duty cycle during TDM + * operation. Therefore we can use the device's I2S mode with + * its delaying of the 1st data bit to receive DSP_A formatted + * data. See device datasheet for additional details. + */ + serial_format = TAS5720_SAIF_I2S; + break; + case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF): + /* + * Similar to DSP_A, we can use the fact that the TAS5720 does + * not care about the LRCLK duty cycle during TDM to receive + * DSP_B formatted data in LEFTJ mode (no delaying of the 1st + * data bit). + */ + serial_format = TAS5720_SAIF_LEFTJ; + break; + case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): + /* No delay after the frame sync */ + serial_format = TAS5720_SAIF_LEFTJ; + break; + default: + dev_vdbg(codec->dev, "DAI Format is not found\n"); + return -EINVAL; + } + + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_SAIF_FORMAT_MASK, + serial_format); + if (ret < 0) { + dev_err(codec->dev, "error setting SAIF format: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int first_slot; + int ret; + + if (!tx_mask) { + dev_err(codec->dev, "tx masks must not be 0\n"); + return -EINVAL; + } + + /* + * Determine the first slot that is being requested. We will only + * use the first slot that is found since the TAS5720 is a mono + * amplifier. + */ + first_slot = __ffs(tx_mask); + + if (first_slot > 7) { + dev_err(codec->dev, "slot selection out of bounds (%u)\n", + first_slot); + return -EINVAL; + } + + /* Enable manual TDM slot selection (instead of I2C ID based) */ + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_TDM_CFG_SRC, TAS5720_TDM_CFG_SRC); + if (ret < 0) + goto error_snd_soc_update_bits; + + /* Configure the TDM slot to process audio from */ + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_TDM_SLOT_SEL_MASK, first_slot); + if (ret < 0) + goto error_snd_soc_update_bits; + + return 0; + +error_snd_soc_update_bits: + dev_err(codec->dev, "error configuring TDM mode: %d\n", ret); + return ret; +} + +static int tas5720_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int ret; + + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_MUTE, mute ? TAS5720_MUTE : 0); + if (ret < 0) { + dev_err(codec->dev, "error (un-)muting device: %d\n", ret); + return ret; + } + + return 0; +} + +static void tas5720_fault_check_work(struct work_struct *work) +{ + struct tas5720_data *tas5720 = container_of(work, struct tas5720_data, + fault_check_work.work); + struct device *dev = tas5720->codec->dev; + unsigned int curr_fault; + int ret; + + ret = regmap_read(tas5720->regmap, TAS5720_FAULT_REG, &curr_fault); + if (ret < 0) { + dev_err(dev, "failed to read FAULT register: %d\n", ret); + goto out; + } + + /* Check/handle all errors except SAIF clock errors */ + curr_fault &= TAS5720_OCE | TAS5720_DCE | TAS5720_OTE; + + /* + * Only flag errors once for a given occurrence. This is needed as + * the TAS5720 will take time clearing the fault condition internally + * during which we don't want to bombard the system with the same + * error message over and over. + */ + if ((curr_fault & TAS5720_OCE) && !(tas5720->last_fault & TAS5720_OCE)) + dev_crit(dev, "experienced an over current hardware fault\n"); + + if ((curr_fault & TAS5720_DCE) && !(tas5720->last_fault & TAS5720_DCE)) + dev_crit(dev, "experienced a DC detection fault\n"); + + if ((curr_fault & TAS5720_OTE) && !(tas5720->last_fault & TAS5720_OTE)) + dev_crit(dev, "experienced an over temperature fault\n"); + + /* Store current fault value so we can detect any changes next time */ + tas5720->last_fault = curr_fault; + + if (!curr_fault) + goto out; + + /* + * Periodically toggle SDZ (shutdown bit) H->L->H to clear any latching + * faults as long as a fault condition persists. Always going through + * the full sequence no matter the first return value to minimizes + * chances for the device to end up in shutdown mode. + */ + ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) + dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); + + ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, TAS5720_SDZ); + if (ret < 0) + dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); + +out: + /* Schedule the next fault check at the specified interval */ + schedule_delayed_work(&tas5720->fault_check_work, + msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); +} + +static int tas5720_codec_probe(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + unsigned int device_id; + int ret; + + tas5720->codec = codec; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret != 0) { + dev_err(codec->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_read(tas5720->regmap, TAS5720_DEVICE_ID_REG, &device_id); + if (ret < 0) { + dev_err(codec->dev, "failed to read device ID register: %d\n", + ret); + goto probe_fail; + } + + if (device_id != TAS5720_DEVICE_ID) { + dev_err(codec->dev, "wrong device ID. expected: %u read: %u\n", + TAS5720_DEVICE_ID, device_id); + ret = -ENODEV; + goto probe_fail; + } + + /* Set device to mute */ + ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_MUTE, TAS5720_MUTE); + if (ret < 0) + goto error_snd_soc_update_bits; + + /* + * Enter shutdown mode - our default when not playing audio - to + * minimize current consumption. On the TAS5720 there is no real down + * side doing so as all device registers are preserved and the wakeup + * of the codec is rather quick which we do using a dapm widget. + */ + ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) + goto error_snd_soc_update_bits; + + INIT_DELAYED_WORK(&tas5720->fault_check_work, tas5720_fault_check_work); + + return 0; + +error_snd_soc_update_bits: + dev_err(codec->dev, "error configuring device registers: %d\n", ret); + +probe_fail: + regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + return ret; +} + +static int tas5720_codec_remove(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + cancel_delayed_work_sync(&tas5720->fault_check_work); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) + dev_err(codec->dev, "failed to disable supplies: %d\n", ret); + + return ret; +}; + +static int tas5720_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Take TAS5720 out of shutdown mode */ + ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, TAS5720_SDZ); + if (ret < 0) { + dev_err(codec->dev, "error waking codec: %d\n", ret); + return ret; + } + + /* + * Observe codec shutdown-to-active time. The datasheet only + * lists a nominal value however just use-it as-is without + * additional padding to minimize the delay introduced in + * starting to play audio (actually there is other setup done + * by the ASoC framework that will provide additional delays, + * so we should always be safe). + */ + msleep(25); + + /* Turn on TAS5720 periodic fault checking/handling */ + tas5720->last_fault = 0; + schedule_delayed_work(&tas5720->fault_check_work, + msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); + } else if (event & SND_SOC_DAPM_PRE_PMD) { + /* Disable TAS5720 periodic fault checking/handling */ + cancel_delayed_work_sync(&tas5720->fault_check_work); + + /* Place TAS5720 in shutdown mode to minimize current draw */ + ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) { + dev_err(codec->dev, "error shutting down codec: %d\n", + ret); + return ret; + } + } + + return 0; +} + +#ifdef CONFIG_PM +static int tas5720_suspend(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + regcache_cache_only(tas5720->regmap, true); + regcache_mark_dirty(tas5720->regmap); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) + dev_err(codec->dev, "failed to disable supplies: %d\n", ret); + + return ret; +} + +static int tas5720_resume(struct snd_soc_codec *codec) +{ + struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) { + dev_err(codec->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(tas5720->regmap, false); + + ret = regcache_sync(tas5720->regmap); + if (ret < 0) { + dev_err(codec->dev, "failed to sync regcache: %d\n", ret); + return ret; + } + + return 0; +} +#else +#define tas5720_suspend NULL +#define tas5720_resume NULL +#endif + +static bool tas5720_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS5720_DEVICE_ID_REG: + case TAS5720_FAULT_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config tas5720_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TAS5720_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tas5720_is_volatile_reg, +}; + +/* + * DAC analog gain. There are four discrete values to select from, ranging + * from 19.2 dB to 26.3dB. + */ +static const DECLARE_TLV_DB_RANGE(dac_analog_tlv, + 0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0), + 0x1, 0x1, TLV_DB_SCALE_ITEM(2070, 0, 0), + 0x2, 0x2, TLV_DB_SCALE_ITEM(2350, 0, 0), + 0x3, 0x3, TLV_DB_SCALE_ITEM(2630, 0, 0), +); + +/* + * DAC digital volumes. From -103.5 to 24 dB in 0.5 dB steps. Note that + * setting the gain below -100 dB (register value <0x7) is effectively a MUTE + * as per device datasheet. + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -10350, 50, 0); + +static const struct snd_kcontrol_new tas5720_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Driver Playback Volume", + TAS5720_VOLUME_CTRL_REG, 0, 0xff, 0, dac_tlv), + SOC_SINGLE_TLV("Speaker Driver Analog Gain", TAS5720_ANALOG_CTRL_REG, + TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), +}; + +static const struct snd_soc_dapm_widget tas5720_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas5720_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUTPUT("OUT") +}; + +static const struct snd_soc_dapm_route tas5720_audio_map[] = { + { "DAC", NULL, "DAC IN" }, + { "OUT", NULL, "DAC" }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_tas5720 = { + .probe = tas5720_codec_probe, + .remove = tas5720_codec_remove, + .suspend = tas5720_suspend, + .resume = tas5720_resume, + + .controls = tas5720_snd_controls, + .num_controls = ARRAY_SIZE(tas5720_snd_controls), + .dapm_widgets = tas5720_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), + .dapm_routes = tas5720_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), +}; + +/* PCM rates supported by the TAS5720 driver */ +#define TAS5720_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +/* Formats supported by TAS5720 driver */ +#define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops tas5720_speaker_dai_ops = { + .hw_params = tas5720_hw_params, + .set_fmt = tas5720_set_dai_fmt, + .set_tdm_slot = tas5720_set_dai_tdm_slot, + .digital_mute = tas5720_mute, +}; + +/* + * TAS5720 DAI structure + * + * Note that were are advertising .playback.channels_max = 2 despite this being + * a mono amplifier. The reason for that is that some serial ports such as TI's + * McASP module have a minimum number of channels (2) that they can output. + * Advertising more channels than we have will allow us to interface with such + * a serial port without really any negative side effects as the TAS5720 will + * simply ignore any extra channel(s) asides from the one channel that is + * configured to be played back. + */ +static struct snd_soc_dai_driver tas5720_dai[] = { + { + .name = "tas5720-amplifier", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TAS5720_RATES, + .formats = TAS5720_FORMATS, + }, + .ops = &tas5720_speaker_dai_ops, + }, +}; + +static int tas5720_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tas5720_data *data; + int ret; + int i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->tas5720_client = client; + data->regmap = devm_regmap_init_i2c(client, &tas5720_regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(dev, "failed to allocate register map: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data->supplies); i++) + data->supplies[i].supply = tas5720_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), + data->supplies); + if (ret != 0) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + dev_set_drvdata(dev, data); + + ret = snd_soc_register_codec(&client->dev, + &soc_codec_dev_tas5720, + tas5720_dai, ARRAY_SIZE(tas5720_dai)); + if (ret < 0) { + dev_err(dev, "failed to register codec: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_remove(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + snd_soc_unregister_codec(dev); + + return 0; +} + +static const struct i2c_device_id tas5720_id[] = { + { "tas5720", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas5720_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tas5720_of_match[] = { + { .compatible = "ti,tas5720", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tas5720_of_match); +#endif + +static struct i2c_driver tas5720_i2c_driver = { + .driver = { + .name = "tas5720", + .of_match_table = of_match_ptr(tas5720_of_match), + }, + .probe = tas5720_probe, + .remove = tas5720_remove, + .id_table = tas5720_id, +}; + +module_i2c_driver(tas5720_i2c_driver); + +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_DESCRIPTION("TAS5720 Audio amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas5720.h b/sound/soc/codecs/tas5720.h new file mode 100644 index 000000000000..3d077c779b12 --- /dev/null +++ b/sound/soc/codecs/tas5720.h @@ -0,0 +1,90 @@ +/* + * tas5720.h - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier + * + * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * 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. + */ + +#ifndef __TAS5720_H__ +#define __TAS5720_H__ + +/* Register Address Map */ +#define TAS5720_DEVICE_ID_REG 0x00 +#define TAS5720_POWER_CTRL_REG 0x01 +#define TAS5720_DIGITAL_CTRL1_REG 0x02 +#define TAS5720_DIGITAL_CTRL2_REG 0x03 +#define TAS5720_VOLUME_CTRL_REG 0x04 +#define TAS5720_ANALOG_CTRL_REG 0x06 +#define TAS5720_FAULT_REG 0x08 +#define TAS5720_DIGITAL_CLIP2_REG 0x10 +#define TAS5720_DIGITAL_CLIP1_REG 0x11 +#define TAS5720_MAX_REG TAS5720_DIGITAL_CLIP1_REG + +/* TAS5720_DEVICE_ID_REG */ +#define TAS5720_DEVICE_ID 0x01 + +/* TAS5720_POWER_CTRL_REG */ +#define TAS5720_DIG_CLIP_MASK GENMASK(7, 2) +#define TAS5720_SLEEP BIT(1) +#define TAS5720_SDZ BIT(0) + +/* TAS5720_DIGITAL_CTRL1_REG */ +#define TAS5720_HPF_BYPASS BIT(7) +#define TAS5720_TDM_CFG_SRC BIT(6) +#define TAS5720_SSZ_DS BIT(3) +#define TAS5720_SAIF_RIGHTJ_24BIT (0x0) +#define TAS5720_SAIF_RIGHTJ_20BIT (0x1) +#define TAS5720_SAIF_RIGHTJ_18BIT (0x2) +#define TAS5720_SAIF_RIGHTJ_16BIT (0x3) +#define TAS5720_SAIF_I2S (0x4) +#define TAS5720_SAIF_LEFTJ (0x5) +#define TAS5720_SAIF_FORMAT_MASK GENMASK(2, 0) + +/* TAS5720_DIGITAL_CTRL2_REG */ +#define TAS5720_MUTE BIT(4) +#define TAS5720_TDM_SLOT_SEL_MASK GENMASK(2, 0) + +/* TAS5720_ANALOG_CTRL_REG */ +#define TAS5720_PWM_RATE_6_3_FSYNC (0x0 << 4) +#define TAS5720_PWM_RATE_8_4_FSYNC (0x1 << 4) +#define TAS5720_PWM_RATE_10_5_FSYNC (0x2 << 4) +#define TAS5720_PWM_RATE_12_6_FSYNC (0x3 << 4) +#define TAS5720_PWM_RATE_14_7_FSYNC (0x4 << 4) +#define TAS5720_PWM_RATE_16_8_FSYNC (0x5 << 4) +#define TAS5720_PWM_RATE_20_10_FSYNC (0x6 << 4) +#define TAS5720_PWM_RATE_24_12_FSYNC (0x7 << 4) +#define TAS5720_PWM_RATE_MASK GENMASK(6, 4) +#define TAS5720_ANALOG_GAIN_19_2DBV (0x0 << 2) +#define TAS5720_ANALOG_GAIN_20_7DBV (0x1 << 2) +#define TAS5720_ANALOG_GAIN_23_5DBV (0x2 << 2) +#define TAS5720_ANALOG_GAIN_26_3DBV (0x3 << 2) +#define TAS5720_ANALOG_GAIN_MASK GENMASK(3, 2) +#define TAS5720_ANALOG_GAIN_SHIFT (0x2) + +/* TAS5720_FAULT_REG */ +#define TAS5720_OC_THRESH_100PCT (0x0 << 4) +#define TAS5720_OC_THRESH_75PCT (0x1 << 4) +#define TAS5720_OC_THRESH_50PCT (0x2 << 4) +#define TAS5720_OC_THRESH_25PCT (0x3 << 4) +#define TAS5720_OC_THRESH_MASK GENMASK(5, 4) +#define TAS5720_CLKE BIT(3) +#define TAS5720_OCE BIT(2) +#define TAS5720_DCE BIT(1) +#define TAS5720_OTE BIT(0) +#define TAS5720_FAULT_MASK GENMASK(3, 0) + +/* TAS5720_DIGITAL_CLIP1_REG */ +#define TAS5720_CLIP1_MASK GENMASK(7, 2) +#define TAS5720_CLIP1_SHIFT (0x2) + +#endif /* __TAS5720_H__ */ From 7de76b621f77aba456f594e4621eca2e94a146f3 Mon Sep 17 00:00:00 2001 From: Mengdong Lin Date: Wed, 27 Apr 2016 14:52:38 +0800 Subject: [PATCH 061/393] ASoC: topology: Check failure to create a widget Stop loading topology info if error happens when creating a widget. Signed-off-by: Mengdong Lin Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index ca5f82885031..0224a6458f3b 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1500,9 +1500,11 @@ static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, for (i = 0; i < count; i++) { widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; ret = soc_tplg_dapm_widget_create(tplg, widget); - if (ret < 0) + if (ret < 0) { dev_err(tplg->dev, "ASoC: failed to load widget %s\n", widget->name); + return ret; + } } return 0; From 06eb49f72fa57f5a49acdf9f4af84d2d326513b3 Mon Sep 17 00:00:00 2001 From: Mengdong Lin Date: Wed, 27 Apr 2016 14:52:56 +0800 Subject: [PATCH 062/393] ASoC: topology: Check size mismatch of ABI objects before parsing If size mismatch of manifest, ABI headers or elements is found, stop parsing topology info and return the error. New fields may be append to the tail of ABI objects which will cause object size to increase. If user space and kernel use different versions of ABI, size mismatch will be detected here. Signed-off-by: Mengdong Lin Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 0224a6458f3b..29ae3d3a0f8a 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1023,6 +1023,11 @@ static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; + if (control_hdr->size != sizeof(*control_hdr)) { + dev_err(tplg->dev, "ASoC: invalid control size\n"); + return -EINVAL; + } + switch (control_hdr->ops.info) { case SND_SOC_TPLG_CTL_VOLSW: case SND_SOC_TPLG_CTL_STROBE: @@ -1499,6 +1504,11 @@ static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, for (i = 0; i < count; i++) { widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; + if (widget->size != sizeof(*widget)) { + dev_err(tplg->dev, "ASoC: invalid widget size\n"); + return -EINVAL; + } + ret = soc_tplg_dapm_widget_create(tplg, widget); if (ret < 0) { dev_err(tplg->dev, "ASoC: failed to load widget %s\n", @@ -1652,8 +1662,6 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, if (tplg->pass != SOC_TPLG_PASS_PCM_DAI) return 0; - pcm = (struct snd_soc_tplg_pcm *)tplg->pos; - if (soc_tplg_check_elem_count(tplg, sizeof(struct snd_soc_tplg_pcm), count, hdr->payload_size, "PCM DAI")) { @@ -1663,7 +1671,13 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, } /* create the FE DAIs and DAI links */ + pcm = (struct snd_soc_tplg_pcm *)tplg->pos; for (i = 0; i < count; i++) { + if (pcm->size != sizeof(*pcm)) { + dev_err(tplg->dev, "ASoC: invalid pcm size\n"); + return -EINVAL; + } + soc_tplg_pcm_create(tplg, pcm); pcm++; } @@ -1683,6 +1697,11 @@ static int soc_tplg_manifest_load(struct soc_tplg *tplg, return 0; manifest = (struct snd_soc_tplg_manifest *)tplg->pos; + if (manifest->size != sizeof(*manifest)) { + dev_err(tplg->dev, "ASoC: invalid manifest size\n"); + return -EINVAL; + } + tplg->pos += sizeof(struct snd_soc_tplg_manifest); if (tplg->comp && tplg->ops && tplg->ops->manifest) @@ -1699,6 +1718,14 @@ static int soc_valid_header(struct soc_tplg *tplg, if (soc_tplg_get_hdr_offset(tplg) >= tplg->fw->size) return 0; + if (hdr->size != sizeof(*hdr)) { + dev_err(tplg->dev, + "ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n", + hdr->type, soc_tplg_get_hdr_offset(tplg), + tplg->fw->size); + return -EINVAL; + } + /* big endian firmware objects not supported atm */ if (hdr->magic == cpu_to_be32(SND_SOC_TPLG_MAGIC)) { dev_err(tplg->dev, From 1a5658c213116d56a1a38e83588f6636a57d6374 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 28 Apr 2016 01:49:48 +0000 Subject: [PATCH 063/393] ASoC: rsnd: count .probe/.remove for rsnd_mod_call() Current rsnd_mod_call is counting its calling count to avoid unbalanced function pair calling for error cases (ex init <-> quit). SSI parent is now controlled as "mod" on current rsnd driver. Because of this reason, SSI .remove function will be called twice if it was used as SSI parent when user tried unbind. But probe/remove pair were not counted. This patch counts probe/remove functions to avoid it. Special thans Hiep Reported-by: Hiep Cao Minh Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- sound/soc/sh/rcar/rsnd.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index fc89a67258ca..a8f61d79333b 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -276,8 +276,9 @@ struct rsnd_mod { /* * status * - * 0xH0000CB0 + * 0xH0000CBA * + * A 0: probe 1: remove * B 0: init 1: quit * C 0: start 1: stop * @@ -287,19 +288,19 @@ struct rsnd_mod { * H 0: fallback * H 0: hw_params */ +#define __rsnd_mod_shift_probe 0 +#define __rsnd_mod_shift_remove 0 #define __rsnd_mod_shift_init 4 #define __rsnd_mod_shift_quit 4 #define __rsnd_mod_shift_start 8 #define __rsnd_mod_shift_stop 8 -#define __rsnd_mod_shift_probe 28 /* always called */ -#define __rsnd_mod_shift_remove 28 /* always called */ #define __rsnd_mod_shift_irq 28 /* always called */ #define __rsnd_mod_shift_pcm_new 28 /* always called */ #define __rsnd_mod_shift_fallback 28 /* always called */ #define __rsnd_mod_shift_hw_params 28 /* always called */ -#define __rsnd_mod_add_probe 0 -#define __rsnd_mod_add_remove 0 +#define __rsnd_mod_add_probe 1 +#define __rsnd_mod_add_remove -1 #define __rsnd_mod_add_init 1 #define __rsnd_mod_add_quit -1 #define __rsnd_mod_add_start 1 @@ -310,7 +311,7 @@ struct rsnd_mod { #define __rsnd_mod_add_hw_params 0 #define __rsnd_mod_call_probe 0 -#define __rsnd_mod_call_remove 0 +#define __rsnd_mod_call_remove 1 #define __rsnd_mod_call_init 0 #define __rsnd_mod_call_quit 1 #define __rsnd_mod_call_start 0 From ca2cd6bc6663314ba7fbf66ba7b14e099671420e Mon Sep 17 00:00:00 2001 From: anish kumar Date: Wed, 27 Apr 2016 15:39:05 -0700 Subject: [PATCH 064/393] ASoC: Add max98371 codec driver Signed-off-by: anish kumar Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/max98371.txt | 17 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 1 + sound/soc/codecs/max98371.c | 442 ++++++++++++++++++ sound/soc/codecs/max98371.h | 67 +++ 5 files changed, 531 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/max98371.txt create mode 100644 sound/soc/codecs/max98371.c create mode 100644 sound/soc/codecs/max98371.h diff --git a/Documentation/devicetree/bindings/sound/max98371.txt b/Documentation/devicetree/bindings/sound/max98371.txt new file mode 100644 index 000000000000..6c285235e64b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/max98371.txt @@ -0,0 +1,17 @@ +max98371 codec + +This device supports I2C mode only. + +Required properties: + +- compatible : "maxim,max98371" +- reg : The chip select number on the I2C bus + +Example: + +&i2c { + max98371: max98371@0x31 { + compatible = "maxim,max98371"; + reg = <0x31>; + }; +}; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 649e92a252ae..d764f1329042 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -79,6 +79,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C select SND_SOC_MAX98357A if GPIOLIB + select SND_SOC_MAX98371 if I2C select SND_SOC_MAX9867 if I2C select SND_SOC_MAX98925 if I2C select SND_SOC_MAX98926 if I2C @@ -522,6 +523,9 @@ config SND_SOC_MAX98095 config SND_SOC_MAX98357A tristate +config SND_SOC_MAX98371 + tristate + config SND_SOC_MAX9867 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 185a712a7fe7..92ef8719c311 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -74,6 +74,7 @@ snd-soc-max98088-objs := max98088.o snd-soc-max98090-objs := max98090.o snd-soc-max98095-objs := max98095.o snd-soc-max98357a-objs := max98357a.o +snd-soc-max98371-objs := max98371.o snd-soc-max9867-objs := max9867.o snd-soc-max98925-objs := max98925.o snd-soc-max98926-objs := max98926.o diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c new file mode 100644 index 000000000000..21fae0932137 --- /dev/null +++ b/sound/soc/codecs/max98371.c @@ -0,0 +1,442 @@ +/* + * max98371.c -- ALSA SoC Stereo MAX98371 driver + * + * Copyright 2015-16 Maxim Integrated Products + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98371.h" + +static const char *const monomix_text[] = { + "Left", "Right", "LeftRightDiv2", +}; + +static const char *const hpf_cutoff_txt[] = { + "Disable", "DC Block", "50Hz", + "100Hz", "200Hz", "400Hz", "800Hz", +}; + +static SOC_ENUM_SINGLE_DECL(max98371_monomix, MAX98371_MONOMIX_CFG, 0, + monomix_text); + +static SOC_ENUM_SINGLE_DECL(max98371_hpf_cutoff, MAX98371_HPF, 0, + hpf_cutoff_txt); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_min_gain, + 0, 1, TLV_DB_SCALE_ITEM(537, 66, 0), + 2, 3, TLV_DB_SCALE_ITEM(677, 82, 0), + 4, 5, TLV_DB_SCALE_ITEM(852, 104, 0), + 6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0), + 8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0), + 10, 11, TLV_DB_SCALE_ITEM(1699, 101, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_max_gain, + 0, 1, TLV_DB_SCALE_ITEM(537, 66, 0), + 2, 3, TLV_DB_SCALE_ITEM(677, 82, 0), + 4, 5, TLV_DB_SCALE_ITEM(852, 104, 0), + 6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0), + 8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0), + 10, 11, TLV_DB_SCALE_ITEM(1699, 208, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_rot_gain, + 0, 1, TLV_DB_SCALE_ITEM(-50, -50, 0), + 2, 6, TLV_DB_SCALE_ITEM(-100, -100, 0), + 7, 8, TLV_DB_SCALE_ITEM(-800, -200, 0), + 9, 11, TLV_DB_SCALE_ITEM(-1200, -300, 0), + 12, 13, TLV_DB_SCALE_ITEM(-2000, -200, 0), + 14, 15, TLV_DB_SCALE_ITEM(-2500, -500, 0), +); + +static const struct reg_default max98371_reg[] = { + { 0x01, 0x00 }, + { 0x02, 0x00 }, + { 0x03, 0x00 }, + { 0x04, 0x00 }, + { 0x05, 0x00 }, + { 0x06, 0x00 }, + { 0x07, 0x00 }, + { 0x08, 0x00 }, + { 0x09, 0x00 }, + { 0x0A, 0x00 }, + { 0x10, 0x06 }, + { 0x11, 0x08 }, + { 0x14, 0x80 }, + { 0x15, 0x00 }, + { 0x16, 0x00 }, + { 0x18, 0x00 }, + { 0x19, 0x00 }, + { 0x1C, 0x00 }, + { 0x1D, 0x00 }, + { 0x1E, 0x00 }, + { 0x1F, 0x00 }, + { 0x20, 0x00 }, + { 0x21, 0x00 }, + { 0x22, 0x00 }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x00 }, + { 0x28, 0x00 }, + { 0x29, 0x00 }, + { 0x2A, 0x00 }, + { 0x2B, 0x00 }, + { 0x2C, 0x00 }, + { 0x2D, 0x00 }, + { 0x2E, 0x0B }, + { 0x31, 0x00 }, + { 0x32, 0x18 }, + { 0x33, 0x00 }, + { 0x34, 0x00 }, + { 0x36, 0x00 }, + { 0x37, 0x00 }, + { 0x38, 0x00 }, + { 0x39, 0x00 }, + { 0x3A, 0x00 }, + { 0x3B, 0x00 }, + { 0x3B, 0x00 }, + { 0x3C, 0x00 }, + { 0x3D, 0x00 }, + { 0x3E, 0x00 }, + { 0x3F, 0x00 }, + { 0x40, 0x00 }, + { 0x41, 0x00 }, + { 0x42, 0x00 }, + { 0x43, 0x00 }, + { 0x4A, 0x00 }, + { 0x4B, 0x00 }, + { 0x4C, 0x00 }, + { 0x4D, 0x00 }, + { 0x4E, 0x00 }, + { 0x50, 0x00 }, + { 0x51, 0x00 }, + { 0x55, 0x00 }, + { 0x58, 0x00 }, + { 0x59, 0x00 }, + { 0x5C, 0x00 }, + { 0xFF, 0x43 }, +}; + +static bool max98371_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98371_IRQ_CLEAR1: + case MAX98371_IRQ_CLEAR2: + case MAX98371_IRQ_CLEAR3: + case MAX98371_VERSION: + return true; + default: + return false; + } +} + +static bool max98371_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98371_SOFT_RESET: + return false; + default: + return true; + } +}; + +static const DECLARE_TLV_DB_RANGE(max98371_gain_tlv, + 0, 7, TLV_DB_SCALE_ITEM(0, 50, 0), + 8, 10, TLV_DB_SCALE_ITEM(400, 100, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98371_noload_gain_tlv, + 0, 11, TLV_DB_SCALE_ITEM(950, 100, 0), +); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -6300, 50, 1); + +static const struct snd_kcontrol_new max98371_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98371_GAIN, + MAX98371_GAIN_SHIFT, (1<codec; + struct max98371_priv *max98371 = snd_soc_codec_get_drvdata(codec); + unsigned int val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + dev_err(codec->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= 0; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= MAX98371_DAI_RIGHT; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= MAX98371_DAI_LEFT; + break; + default: + dev_err(codec->dev, "DAI wrong mode unsupported"); + return -EINVAL; + } + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MODE_MASK, val); + return 0; +} + +static int max98371_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98371_priv *max98371 = snd_soc_codec_get_drvdata(codec); + int blr_clk_ratio, ch_size, channels = params_channels(params); + int rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16); + ch_size = 8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16); + ch_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32); + ch_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32); + ch_size = 32; + break; + default: + return -EINVAL; + } + + /* BCLK/LRCLK ratio calculation */ + blr_clk_ratio = channels * ch_size; + switch (blr_clk_ratio) { + case 32: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_32); + break; + case 48: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_48); + break; + case 64: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_64); + break; + default: + return -EINVAL; + } + + switch (rate) { + case 32000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_32); + break; + case 44100: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_44); + break; + case 48000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_48); + break; + case 88200: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_88); + break; + case 96000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_96); + break; + default: + return -EINVAL; + } + + /* enabling both the RX channels*/ + regmap_update_bits(max98371->regmap, MAX98371_MONOMIX_SRC, + MAX98371_MONOMIX_SRC_MASK, MONOMIX_RX_0_1); + regmap_update_bits(max98371->regmap, MAX98371_DAI_CHANNEL, + MAX98371_CHANNEL_MASK, MAX98371_CHANNEL_MASK); + return 0; +} + +static const struct snd_soc_dapm_widget max98371_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, MAX98371_SPK_ENABLE, 0, 0), + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98371_GLOBAL_ENABLE, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("SPK_OUT"), +}; + +static const struct snd_soc_dapm_route max98371_audio_map[] = { + {"DAC", NULL, "HiFi Playback"}, + {"SPK_OUT", NULL, "DAC"}, + {"SPK_OUT", NULL, "Global Enable"}, +}; + +#define MAX98371_RATES SNDRV_PCM_RATE_8000_48000 +#define MAX98371_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +static const struct snd_soc_dai_ops max98371_dai_ops = { + .set_fmt = max98371_dai_set_fmt, + .hw_params = max98371_dai_hw_params, +}; + +static struct snd_soc_dai_driver max98371_dai[] = { + { + .name = "max98371-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98371_FORMATS, + }, + .ops = &max98371_dai_ops, + } +}; + +static const struct snd_soc_codec_driver max98371_codec = { + .controls = max98371_snd_controls, + .num_controls = ARRAY_SIZE(max98371_snd_controls), + .dapm_routes = max98371_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98371_audio_map), + .dapm_widgets = max98371_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98371_dapm_widgets), +}; + +static const struct regmap_config max98371_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX98371_VERSION, + .reg_defaults = max98371_reg, + .num_reg_defaults = ARRAY_SIZE(max98371_reg), + .volatile_reg = max98371_volatile_register, + .readable_reg = max98371_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98371_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max98371_priv *max98371; + int ret, reg; + + max98371 = devm_kzalloc(&i2c->dev, + sizeof(*max98371), GFP_KERNEL); + if (!max98371) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98371); + max98371->regmap = devm_regmap_init_i2c(i2c, &max98371_regmap); + if (IS_ERR(max98371->regmap)) { + ret = PTR_ERR(max98371->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + ret = regmap_read(max98371->regmap, MAX98371_VERSION, ®); + if (ret < 0) { + dev_info(&i2c->dev, "device error %d\n", ret); + return ret; + } + dev_info(&i2c->dev, "device version %x\n", reg); + + ret = snd_soc_register_codec(&i2c->dev, &max98371_codec, + max98371_dai, ARRAY_SIZE(max98371_dai)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + return ret; + } + return ret; +} + +static int max98371_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id max98371_i2c_id[] = { + { "max98371", 0 }, +}; + +MODULE_DEVICE_TABLE(i2c, max98371_i2c_id); + +static const struct of_device_id max98371_of_match[] = { + { .compatible = "maxim,max98371", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98371_of_match); + +static struct i2c_driver max98371_i2c_driver = { + .driver = { + .name = "max98371", + .owner = THIS_MODULE, + .pm = NULL, + .of_match_table = of_match_ptr(max98371_of_match), + }, + .probe = max98371_i2c_probe, + .remove = max98371_i2c_remove, + .id_table = max98371_i2c_id, +}; + +module_i2c_driver(max98371_i2c_driver); + +MODULE_AUTHOR("anish kumar "); +MODULE_DESCRIPTION("ALSA SoC MAX98371 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98371.h b/sound/soc/codecs/max98371.h new file mode 100644 index 000000000000..9f6330964d98 --- /dev/null +++ b/sound/soc/codecs/max98371.h @@ -0,0 +1,67 @@ +/* + * max98371.h -- MAX98371 ALSA SoC Audio driver + * + * Copyright 2011-2012 Maxim Integrated Products + * + * 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. + */ + +#ifndef _MAX98371_H +#define _MAX98371_H + +#define MAX98371_IRQ_CLEAR1 0x01 +#define MAX98371_IRQ_CLEAR2 0x02 +#define MAX98371_IRQ_CLEAR3 0x03 +#define MAX98371_DAI_CLK 0x10 +#define MAX98371_DAI_BSEL_MASK 0xF +#define MAX98371_DAI_BSEL_32 2 +#define MAX98371_DAI_BSEL_48 3 +#define MAX98371_DAI_BSEL_64 4 +#define MAX98371_SPK_SR 0x11 +#define MAX98371_SPK_SR_MASK 0xF +#define MAX98371_SPK_SR_32 6 +#define MAX98371_SPK_SR_44 7 +#define MAX98371_SPK_SR_48 8 +#define MAX98371_SPK_SR_88 10 +#define MAX98371_SPK_SR_96 11 +#define MAX98371_DAI_CHANNEL 0x15 +#define MAX98371_CHANNEL_MASK 0x3 +#define MAX98371_MONOMIX_SRC 0x18 +#define MAX98371_MONOMIX_CFG 0x19 +#define MAX98371_HPF 0x1C +#define MAX98371_MONOMIX_SRC_MASK 0xFF +#define MONOMIX_RX_0_1 ((0x1)<<(4)) +#define M98371_DAI_CHANNEL_I2S 0x3 +#define MAX98371_DIGITAL_GAIN 0x2D +#define MAX98371_DIGITAL_GAIN_WIDTH 0x7 +#define MAX98371_GAIN 0x2E +#define MAX98371_GAIN_SHIFT 0x4 +#define MAX98371_GAIN_WIDTH 0x4 +#define MAX98371_DHT_MAX_WIDTH 4 +#define MAX98371_FMT 0x14 +#define MAX98371_CHANSZ_WIDTH 6 +#define MAX98371_FMT_MASK ((0x3)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_FMT_MODE_MASK ((0x7)<<(3)) +#define MAX98371_DAI_LEFT ((0x1)<<(3)) +#define MAX98371_DAI_RIGHT ((0x2)<<(3)) +#define MAX98371_DAI_CHANSZ_16 ((1)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DAI_CHANSZ_24 ((2)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DAI_CHANSZ_32 ((3)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DHT 0x32 +#define MAX98371_DHT_STEP 0x3 +#define MAX98371_DHT_GAIN 0x31 +#define MAX98371_DHT_GAIN_WIDTH 0x4 +#define MAX98371_DHT_ROT_WIDTH 0x4 +#define MAX98371_SPK_ENABLE 0x4A +#define MAX98371_GLOBAL_ENABLE 0x50 +#define MAX98371_SOFT_RESET 0x51 +#define MAX98371_VERSION 0xFF + + +struct max98371_priv { + struct regmap *regmap; + struct snd_soc_codec *codec; +}; +#endif From ceeb0ae7bf42a3deaaaee981d2da02e5d3ad2b0f Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 065/393] btrfs: sink gfp parameter to set_extent_bits All callers pass GFP_NOFS. Signed-off-by: David Sterba --- fs/btrfs/extent-tree.c | 4 ++-- fs/btrfs/extent_io.c | 5 ++--- fs/btrfs/extent_io.h | 4 ++-- fs/btrfs/file-item.c | 2 +- fs/btrfs/relocation.c | 4 ++-- fs/btrfs/scrub.c | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index 84e060eb0de8..0cfddbe8abba 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -231,9 +231,9 @@ static int add_excluded_extent(struct btrfs_root *root, { u64 end = start + num_bytes - 1; set_extent_bits(&root->fs_info->freed_extents[0], - start, end, EXTENT_UPTODATE, GFP_NOFS); + start, end, EXTENT_UPTODATE); set_extent_bits(&root->fs_info->freed_extents[1], - start, end, EXTENT_UPTODATE, GFP_NOFS); + start, end, EXTENT_UPTODATE); return 0; } diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index d247fc0eea19..3b53d217066c 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -2232,13 +2232,12 @@ int btrfs_get_io_failure_record(struct inode *inode, u64 start, u64 end, /* set the bits in the private failure tree */ ret = set_extent_bits(failure_tree, start, end, - EXTENT_LOCKED | EXTENT_DIRTY, GFP_NOFS); + EXTENT_LOCKED | EXTENT_DIRTY); if (ret >= 0) ret = set_state_failrec(failure_tree, start, failrec); /* set the bits in the inode's tree */ if (ret >= 0) - ret = set_extent_bits(tree, start, end, EXTENT_DAMAGED, - GFP_NOFS); + ret = set_extent_bits(tree, start, end, EXTENT_DAMAGED); if (ret < 0) { kfree(failrec); return ret; diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index b5e0ade90e88..447c6e6ff88c 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -259,9 +259,9 @@ int set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, struct extent_state **cached_state, gfp_t mask); static inline int set_extent_bits(struct extent_io_tree *tree, u64 start, - u64 end, unsigned bits, gfp_t mask) + u64 end, unsigned bits) { - return set_extent_bit(tree, start, end, bits, NULL, NULL, mask); + return set_extent_bit(tree, start, end, bits, NULL, NULL, GFP_NOFS); } static inline int clear_extent_uptodate(struct extent_io_tree *tree, u64 start, diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c index 7a7d6e253cfc..62a81ee13a5f 100644 --- a/fs/btrfs/file-item.c +++ b/fs/btrfs/file-item.c @@ -248,7 +248,7 @@ static int __btrfs_lookup_bio_sums(struct btrfs_root *root, BTRFS_DATA_RELOC_TREE_OBJECTID) { set_extent_bits(io_tree, offset, offset + root->sectorsize - 1, - EXTENT_NODATASUM, GFP_NOFS); + EXTENT_NODATASUM); } else { btrfs_info(BTRFS_I(inode)->root->fs_info, "no csum found for inode %llu start %llu", diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index 08ef890deca6..78cbfb530de6 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -2814,7 +2814,7 @@ static void mark_block_processed(struct reloc_control *rc, u64 bytenr, u32 blocksize) { set_extent_bits(&rc->processed_blocks, bytenr, bytenr + blocksize - 1, - EXTENT_DIRTY, GFP_NOFS); + EXTENT_DIRTY); } static void __mark_block_processed(struct reloc_control *rc, @@ -3182,7 +3182,7 @@ static int relocate_file_extent_cluster(struct inode *inode, page_start + offset == cluster->boundary[nr]) { set_extent_bits(&BTRFS_I(inode)->io_tree, page_start, page_end, - EXTENT_BOUNDARY, GFP_NOFS); + EXTENT_BOUNDARY); nr++; } diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index 4678f03e878e..9d24375eed1f 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -745,7 +745,7 @@ static int scrub_fixup_readpage(u64 inum, u64 offset, u64 root, void *fixup_ctx) * sure we read the bad mirror. */ ret = set_extent_bits(&BTRFS_I(inode)->io_tree, offset, end, - EXTENT_DAMAGED, GFP_NOFS); + EXTENT_DAMAGED); if (ret) { /* set_extent_bits should give proper error */ WARN_ON(ret > 0); From 91166212e0ffbb4db50aa8a238548e967008b33d Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 066/393] btrfs: sink gfp parameter to clear_extent_bits Callers pass GFP_NOFS and GFP_KERNEL. No need to pass the flags around. Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 2 +- fs/btrfs/extent-tree.c | 8 ++++---- fs/btrfs/extent_io.c | 4 ++-- fs/btrfs/extent_io.h | 5 +++-- fs/btrfs/inode.c | 3 +-- fs/btrfs/relocation.c | 3 +-- fs/btrfs/scrub.c | 2 +- fs/btrfs/tests/extent-io-tests.c | 2 +- fs/btrfs/tree-log.c | 2 +- 9 files changed, 15 insertions(+), 16 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 4e47849d7427..3bc88b4b0032 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -4357,7 +4357,7 @@ static int btrfs_destroy_marked_extents(struct btrfs_root *root, if (ret) break; - clear_extent_bits(dirty_pages, start, end, mark, GFP_NOFS); + clear_extent_bits(dirty_pages, start, end, mark); while (start <= end) { eb = btrfs_find_tree_block(root->fs_info, start); start += root->nodesize; diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index 0cfddbe8abba..b1c6d7634e88 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -246,9 +246,9 @@ static void free_excluded_extents(struct btrfs_root *root, end = start + cache->key.offset - 1; clear_extent_bits(&root->fs_info->freed_extents[0], - start, end, EXTENT_UPTODATE, GFP_NOFS); + start, end, EXTENT_UPTODATE); clear_extent_bits(&root->fs_info->freed_extents[1], - start, end, EXTENT_UPTODATE, GFP_NOFS); + start, end, EXTENT_UPTODATE); } static int exclude_super_stripes(struct btrfs_root *root, @@ -10526,14 +10526,14 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) */ mutex_lock(&fs_info->unused_bg_unpin_mutex); ret = clear_extent_bits(&fs_info->freed_extents[0], start, end, - EXTENT_DIRTY, GFP_NOFS); + EXTENT_DIRTY); if (ret) { mutex_unlock(&fs_info->unused_bg_unpin_mutex); btrfs_dec_block_group_ro(root, block_group); goto end_trans; } ret = clear_extent_bits(&fs_info->freed_extents[1], start, end, - EXTENT_DIRTY, GFP_NOFS); + EXTENT_DIRTY); if (ret) { mutex_unlock(&fs_info->unused_bg_unpin_mutex); btrfs_dec_block_group_ro(root, block_group); diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 3b53d217066c..2946c1522870 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1975,13 +1975,13 @@ int free_io_failure(struct inode *inode, struct io_failure_record *rec) set_state_failrec(failure_tree, rec->start, NULL); ret = clear_extent_bits(failure_tree, rec->start, rec->start + rec->len - 1, - EXTENT_LOCKED | EXTENT_DIRTY, GFP_NOFS); + EXTENT_LOCKED | EXTENT_DIRTY); if (ret) err = ret; ret = clear_extent_bits(&BTRFS_I(inode)->io_tree, rec->start, rec->start + rec->len - 1, - EXTENT_DAMAGED, GFP_NOFS); + EXTENT_DAMAGED); if (ret && !err) err = ret; diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index 447c6e6ff88c..90c5c8176162 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -241,14 +241,15 @@ static inline int unlock_extent_cached(struct extent_io_tree *tree, u64 start, } static inline int clear_extent_bits(struct extent_io_tree *tree, u64 start, - u64 end, unsigned bits, gfp_t mask) + u64 end, unsigned bits) { int wake = 0; if (bits & EXTENT_LOCKED) wake = 1; - return clear_extent_bit(tree, start, end, bits, wake, 0, NULL, mask); + return clear_extent_bit(tree, start, end, bits, wake, 0, NULL, + GFP_NOFS); } int set_record_extent_bits(struct extent_io_tree *tree, u64 start, u64 end, diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 2aaba58b4856..d37997f4eb98 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3103,8 +3103,7 @@ static int btrfs_readpage_end_io_hook(struct btrfs_io_bio *io_bio, if (root->root_key.objectid == BTRFS_DATA_RELOC_TREE_OBJECTID && test_range_bit(io_tree, start, end, EXTENT_NODATASUM, 1, NULL)) { - clear_extent_bits(io_tree, start, end, EXTENT_NODATASUM, - GFP_NOFS); + clear_extent_bits(io_tree, start, end, EXTENT_NODATASUM); return 0; } diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index 78cbfb530de6..bd5ea1a8a9f1 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -4059,8 +4059,7 @@ static noinline_for_stack int relocate_block_group(struct reloc_control *rc) } btrfs_release_path(path); - clear_extent_bits(&rc->processed_blocks, 0, (u64)-1, EXTENT_DIRTY, - GFP_NOFS); + clear_extent_bits(&rc->processed_blocks, 0, (u64)-1, EXTENT_DIRTY); if (trans) { btrfs_end_transaction_throttle(trans, rc->extent_root); diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index 9d24375eed1f..841dae874a21 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -763,7 +763,7 @@ static int scrub_fixup_readpage(u64 inum, u64 offset, u64 root, void *fixup_ctx) end, EXTENT_DAMAGED, 0, NULL); if (!corrected) clear_extent_bits(&BTRFS_I(inode)->io_tree, offset, end, - EXTENT_DAMAGED, GFP_NOFS); + EXTENT_DAMAGED); } out: diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c index 70948b13bc81..88e28e5d2c38 100644 --- a/fs/btrfs/tests/extent-io-tests.c +++ b/fs/btrfs/tests/extent-io-tests.c @@ -262,7 +262,7 @@ static int test_find_delalloc(void) } ret = 0; out_bits: - clear_extent_bits(&tmp, 0, total_dirty - 1, (unsigned)-1, GFP_KERNEL); + clear_extent_bits(&tmp, 0, total_dirty - 1, (unsigned)-1); out: if (locked_page) put_page(locked_page); diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 517d0ccb351e..a111e275ab9e 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -3001,7 +3001,7 @@ static void free_log_tree(struct btrfs_trans_handle *trans, break; clear_extent_bits(&log->dirty_log_pages, start, end, - EXTENT_DIRTY | EXTENT_NEW, GFP_NOFS); + EXTENT_DIRTY | EXTENT_NEW); } /* From f734c44a1bfffd762f6f5829cb41224d267b80d9 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 067/393] btrfs: sink gfp parameter to clear_record_extent_bits Callers pass GFP_NOFS. No need to pass the flags around. Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 5 ++--- fs/btrfs/extent_io.h | 3 +-- fs/btrfs/qgroup.c | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 2946c1522870..164bda63c5ac 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1308,8 +1308,7 @@ int clear_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, } int clear_record_extent_bits(struct extent_io_tree *tree, u64 start, u64 end, - unsigned bits, gfp_t mask, - struct extent_changeset *changeset) + unsigned bits, struct extent_changeset *changeset) { /* * Don't support EXTENT_LOCKED case, same reason as @@ -1317,7 +1316,7 @@ int clear_record_extent_bits(struct extent_io_tree *tree, u64 start, u64 end, */ BUG_ON(bits & EXTENT_LOCKED); - return __clear_extent_bit(tree, start, end, bits, 0, 0, NULL, mask, + return __clear_extent_bit(tree, start, end, bits, 0, 0, NULL, GFP_NOFS, changeset); } diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index 90c5c8176162..b7c258c9fa2d 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -221,8 +221,7 @@ int test_range_bit(struct extent_io_tree *tree, u64 start, u64 end, unsigned bits, int filled, struct extent_state *cached_state); int clear_record_extent_bits(struct extent_io_tree *tree, u64 start, u64 end, - unsigned bits, gfp_t mask, - struct extent_changeset *changeset); + unsigned bits, struct extent_changeset *changeset); int clear_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, unsigned bits, int wake, int delete, struct extent_state **cached, gfp_t mask); diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 9e119552ed32..129392a78986 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -2580,8 +2580,7 @@ static int __btrfs_qgroup_release_data(struct inode *inode, u64 start, u64 len, return -ENOMEM; ret = clear_record_extent_bits(&BTRFS_I(inode)->io_tree, start, - start + len -1, EXTENT_QGROUP_RESERVED, GFP_NOFS, - &changeset); + start + len -1, EXTENT_QGROUP_RESERVED, &changeset); if (ret < 0) goto out; @@ -2688,7 +2687,7 @@ void btrfs_qgroup_check_reserved_leak(struct inode *inode) return; ret = clear_record_extent_bits(&BTRFS_I(inode)->io_tree, 0, (u64)-1, - EXTENT_QGROUP_RESERVED, GFP_NOFS, &changeset); + EXTENT_QGROUP_RESERVED, &changeset); WARN_ON(ret < 0); if (WARN_ON(changeset.bytes_changed)) { From af6f8f604d44e05f98ed45a69830547ed133adf8 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 068/393] btrfs: sink gfp parameter to clear_extent_dirty Callers pass GFP_NOFS. No need to pass the flags around. Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 2 +- fs/btrfs/extent-tree.c | 2 +- fs/btrfs/extent_io.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 3bc88b4b0032..263823f28ba4 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -4392,7 +4392,7 @@ static int btrfs_destroy_pinned_extent(struct btrfs_root *root, if (ret) break; - clear_extent_dirty(unpin, start, end, GFP_NOFS); + clear_extent_dirty(unpin, start, end); btrfs_error_unpin_extent_range(root, start, end); cond_resched(); } diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index b1c6d7634e88..61b3dd25ba4c 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -6408,7 +6408,7 @@ int btrfs_finish_extent_commit(struct btrfs_trans_handle *trans, ret = btrfs_discard_extent(root, start, end + 1 - start, NULL); - clear_extent_dirty(unpin, start, end, GFP_NOFS); + clear_extent_dirty(unpin, start, end); unpin_extent_range(root, start, end, true); mutex_unlock(&fs_info->unused_bg_unpin_mutex); cond_resched(); diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index b7c258c9fa2d..9e987ee03361 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -279,11 +279,11 @@ static inline int set_extent_dirty(struct extent_io_tree *tree, u64 start, } static inline int clear_extent_dirty(struct extent_io_tree *tree, u64 start, - u64 end, gfp_t mask) + u64 end) { return clear_extent_bit(tree, start, end, EXTENT_DIRTY | EXTENT_DELALLOC | - EXTENT_DO_ACCOUNTING, 0, 0, NULL, mask); + EXTENT_DO_ACCOUNTING, 0, 0, NULL, GFP_NOFS); } int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, From 7cd8c7527cb3ad3b397fecfa1bdc8eec9fa33dd8 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 069/393] btrfs: sink gfp parameter to set_extent_delalloc Callers pass GFP_NOFS and tests pass GFP_KERNEL, but using NOFS there does not hurt. No need to pass the flags around. Signed-off-by: David Sterba --- fs/btrfs/extent_io.h | 4 ++-- fs/btrfs/inode.c | 2 +- fs/btrfs/tests/extent-io-tests.c | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index 9e987ee03361..e99a6befdbf5 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -291,11 +291,11 @@ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, struct extent_state **cached_state, gfp_t mask); static inline int set_extent_delalloc(struct extent_io_tree *tree, u64 start, - u64 end, struct extent_state **cached_state, gfp_t mask) + u64 end, struct extent_state **cached_state) { return set_extent_bit(tree, start, end, EXTENT_DELALLOC | EXTENT_UPTODATE, - NULL, cached_state, mask); + NULL, cached_state, GFP_NOFS); } static inline int set_extent_defrag(struct extent_io_tree *tree, u64 start, diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index d37997f4eb98..1a4bd193962c 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -1962,7 +1962,7 @@ int btrfs_set_extent_delalloc(struct inode *inode, u64 start, u64 end, { WARN_ON((end & (PAGE_SIZE - 1)) == 0); return set_extent_delalloc(&BTRFS_I(inode)->io_tree, start, end, - cached_state, GFP_NOFS); + cached_state); } /* see btrfs_writepage_start_hook for details on why this is required */ diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c index 88e28e5d2c38..c190d2a520d1 100644 --- a/fs/btrfs/tests/extent-io-tests.c +++ b/fs/btrfs/tests/extent-io-tests.c @@ -113,7 +113,7 @@ static int test_find_delalloc(void) * |--- delalloc ---| * |--- search ---| */ - set_extent_delalloc(&tmp, 0, 4095, NULL, GFP_KERNEL); + set_extent_delalloc(&tmp, 0, 4095, NULL); start = 0; end = 0; found = find_lock_delalloc_range(inode, &tmp, locked_page, &start, @@ -144,7 +144,7 @@ static int test_find_delalloc(void) test_msg("Couldn't find the locked page\n"); goto out_bits; } - set_extent_delalloc(&tmp, 4096, max_bytes - 1, NULL, GFP_KERNEL); + set_extent_delalloc(&tmp, 4096, max_bytes - 1, NULL); start = test_start; end = 0; found = find_lock_delalloc_range(inode, &tmp, locked_page, &start, @@ -199,7 +199,7 @@ static int test_find_delalloc(void) * * We are re-using our test_start from above since it works out well. */ - set_extent_delalloc(&tmp, max_bytes, total_dirty - 1, NULL, GFP_KERNEL); + set_extent_delalloc(&tmp, max_bytes, total_dirty - 1, NULL); start = test_start; end = 0; found = find_lock_delalloc_range(inode, &tmp, locked_page, &start, From 018ed4f788870fdf6e625d04aa287080bdc8a54f Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 070/393] btrfs: sink gfp parameter to set_extent_defrag Single caller passes GFP_NOFS. Signed-off-by: David Sterba --- fs/btrfs/extent_io.h | 4 ++-- fs/btrfs/ioctl.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index e99a6befdbf5..a1cf4b1dd290 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -299,11 +299,11 @@ static inline int set_extent_delalloc(struct extent_io_tree *tree, u64 start, } static inline int set_extent_defrag(struct extent_io_tree *tree, u64 start, - u64 end, struct extent_state **cached_state, gfp_t mask) + u64 end, struct extent_state **cached_state) { return set_extent_bit(tree, start, end, EXTENT_DELALLOC | EXTENT_UPTODATE | EXTENT_DEFRAG, - NULL, cached_state, mask); + NULL, cached_state, GFP_NOFS); } static inline int set_extent_new(struct extent_io_tree *tree, u64 start, diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 5a23806ae418..72f48252a55d 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -1228,7 +1228,7 @@ static int cluster_pages_for_defrag(struct inode *inode, set_extent_defrag(&BTRFS_I(inode)->io_tree, page_start, page_end - 1, - &cached_state, GFP_NOFS); + &cached_state); unlock_extent_cached(&BTRFS_I(inode)->io_tree, page_start, page_end - 1, &cached_state, From 3744dbeb7033825e53b919ae0887e08e924841a9 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 071/393] btrfs: sink gfp parameter to set_extent_new Single caller passes GFP_NOFS. Signed-off-by: David Sterba --- fs/btrfs/extent-tree.c | 2 +- fs/btrfs/extent_io.h | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index 61b3dd25ba4c..0ddc996a40e1 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -7923,7 +7923,7 @@ btrfs_init_new_buffer(struct btrfs_trans_handle *trans, struct btrfs_root *root, buf->start + buf->len - 1, GFP_NOFS); else set_extent_new(&root->dirty_log_pages, buf->start, - buf->start + buf->len - 1, GFP_NOFS); + buf->start + buf->len - 1); } else { buf->log_index = -1; set_extent_dirty(&trans->transaction->dirty_pages, buf->start, diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index a1cf4b1dd290..566cf86d7e63 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -307,9 +307,10 @@ static inline int set_extent_defrag(struct extent_io_tree *tree, u64 start, } static inline int set_extent_new(struct extent_io_tree *tree, u64 start, - u64 end, gfp_t mask) + u64 end) { - return set_extent_bit(tree, start, end, EXTENT_NEW, NULL, NULL, mask); + return set_extent_bit(tree, start, end, EXTENT_NEW, NULL, NULL, + GFP_NOFS); } static inline int set_extent_uptodate(struct extent_io_tree *tree, u64 start, From 2c53b912ae317e560bce1fc446e76915a5b30587 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 072/393] btrfs: sink gfp parameter to set_record_extent_bits Single caller passes GFP_NOFS. Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 5 ++--- fs/btrfs/extent_io.h | 3 +-- fs/btrfs/qgroup.c | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 164bda63c5ac..0c9b11924f74 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1284,8 +1284,7 @@ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, /* wrappers around set/clear extent bit */ int set_record_extent_bits(struct extent_io_tree *tree, u64 start, u64 end, - unsigned bits, gfp_t mask, - struct extent_changeset *changeset) + unsigned bits, struct extent_changeset *changeset) { /* * We don't support EXTENT_LOCKED yet, as current changeset will @@ -1295,7 +1294,7 @@ int set_record_extent_bits(struct extent_io_tree *tree, u64 start, u64 end, */ BUG_ON(bits & EXTENT_LOCKED); - return __set_extent_bit(tree, start, end, bits, 0, NULL, NULL, mask, + return __set_extent_bit(tree, start, end, bits, 0, NULL, NULL, GFP_NOFS, changeset); } diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index 566cf86d7e63..94b376446042 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -252,8 +252,7 @@ static inline int clear_extent_bits(struct extent_io_tree *tree, u64 start, } int set_record_extent_bits(struct extent_io_tree *tree, u64 start, u64 end, - unsigned bits, gfp_t mask, - struct extent_changeset *changeset); + unsigned bits, struct extent_changeset *changeset); int set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, unsigned bits, u64 *failed_start, struct extent_state **cached_state, gfp_t mask); diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 129392a78986..34af959b4e0f 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -2542,8 +2542,7 @@ int btrfs_qgroup_reserve_data(struct inode *inode, u64 start, u64 len) changeset.bytes_changed = 0; changeset.range_changed = ulist_alloc(GFP_NOFS); ret = set_record_extent_bits(&BTRFS_I(inode)->io_tree, start, - start + len -1, EXTENT_QGROUP_RESERVED, GFP_NOFS, - &changeset); + start + len -1, EXTENT_QGROUP_RESERVED, &changeset); trace_btrfs_qgroup_reserve_data(inode, start, len, changeset.bytes_changed, QGROUP_RESERVE); From b5a4ba14e0caa9da1b3b98939274bdeda71859e3 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 01:02:15 +0200 Subject: [PATCH 073/393] btrfs: untangle gotos a bit in __set_extent_bit Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 0c9b11924f74..f5eda54e225f 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1037,7 +1037,13 @@ __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, goto out; } - goto search_again; +search_again: + if (start > end) + goto out; + spin_unlock(&tree->lock); + if (gfpflags_allow_blocking(mask)) + cond_resched(); + goto again; out: spin_unlock(&tree->lock); @@ -1046,13 +1052,6 @@ __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, return err; -search_again: - if (start > end) - goto out; - spin_unlock(&tree->lock); - if (gfpflags_allow_blocking(mask)) - cond_resched(); - goto again; } int set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, From 7ab5cb2a9e3161de3a288bafd100515b5f44f27e Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 01:02:15 +0200 Subject: [PATCH 074/393] btrfs: untangle gotos a bit in __clear_extent_bit Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index f5eda54e225f..3ecd8b1f239f 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -726,14 +726,6 @@ static int __clear_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, start = last_end + 1; if (start <= end && state && !need_resched()) goto hit_next; - goto search_again; - -out: - spin_unlock(&tree->lock); - if (prealloc) - free_extent_state(prealloc); - - return 0; search_again: if (start > end) @@ -742,6 +734,14 @@ static int __clear_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, if (gfpflags_allow_blocking(mask)) cond_resched(); goto again; + +out: + spin_unlock(&tree->lock); + if (prealloc) + free_extent_state(prealloc); + + return 0; + } static void wait_on_state(struct extent_io_tree *tree, From 03bf5387708069194fbe5072fb57f9542e7a8b82 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 01:02:15 +0200 Subject: [PATCH 075/393] btrfs: untangle gotos a bit in convert_extent_bit Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 3ecd8b1f239f..d39e2241ceb7 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1262,15 +1262,6 @@ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, goto out; } - goto search_again; - -out: - spin_unlock(&tree->lock); - if (prealloc) - free_extent_state(prealloc); - - return err; - search_again: if (start > end) goto out; @@ -1279,6 +1270,13 @@ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, cond_resched(); first_iteration = false; goto again; + +out: + spin_unlock(&tree->lock); + if (prealloc) + free_extent_state(prealloc); + + return err; } /* wrappers around set/clear extent bit */ From 059f791c6bbaba72dc3c1bd6e2657aacc8552849 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Wed, 27 Apr 2016 01:03:45 +0200 Subject: [PATCH 076/393] btrfs: make state preallocation more speculative in __set_extent_bit Similar to __clear_extent_bit, do not fail if the state preallocation fails as we might not need it. One less BUG_ON. Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index d39e2241ceb7..8707bcc615ff 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -873,8 +873,14 @@ __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, bits |= EXTENT_FIRST_DELALLOC; again: if (!prealloc && gfpflags_allow_blocking(mask)) { + /* + * Don't care for allocation failure here because we might end + * up not needing the pre-allocated extent state at all, which + * is the case if we only have in the tree extent states that + * cover our input range and don't cover too any other range. + * If we end up needing a new extent state we allocate it later. + */ prealloc = alloc_extent_state(mask); - BUG_ON(!prealloc); } spin_lock(&tree->lock); From 210aa27768bec4297a9d6ad0e5cab45935c775e9 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Tue, 26 Apr 2016 23:54:39 +0200 Subject: [PATCH 077/393] btrfs: sink gfp parameter to convert_extent_bit Single caller passes GFP_NOFS. We can get rid of the gfpflags_allow_blocking checks as NOFS can block but does not recurse to filesystem through reclaim. Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 12 ++++++------ fs/btrfs/extent_io.h | 2 +- fs/btrfs/transaction.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 8707bcc615ff..c1139bcf8870 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1078,17 +1078,18 @@ int set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, * @bits: the bits to set in this range * @clear_bits: the bits to clear in this range * @cached_state: state that we're going to cache - * @mask: the allocation mask * * This will go through and set bits for the given range. If any states exist * already in this range they are set with the given bit and cleared of the * clear_bits. This is only meant to be used by things that are mergeable, ie * converting from say DELALLOC to DIRTY. This is not meant to be used with * boundary bits like LOCK. + * + * All allocations are done with GFP_NOFS. */ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, unsigned bits, unsigned clear_bits, - struct extent_state **cached_state, gfp_t mask) + struct extent_state **cached_state) { struct extent_state *state; struct extent_state *prealloc = NULL; @@ -1103,7 +1104,7 @@ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, btrfs_debug_check_extent_io_range(tree, start, end); again: - if (!prealloc && gfpflags_allow_blocking(mask)) { + if (!prealloc) { /* * Best effort, don't worry if extent state allocation fails * here for the first iteration. We might have a cached state @@ -1111,7 +1112,7 @@ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, * extent state allocations are needed. We'll only know this * after locking the tree. */ - prealloc = alloc_extent_state(mask); + prealloc = alloc_extent_state(GFP_NOFS); if (!prealloc && !first_iteration) return -ENOMEM; } @@ -1272,8 +1273,7 @@ int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, if (start > end) goto out; spin_unlock(&tree->lock); - if (gfpflags_allow_blocking(mask)) - cond_resched(); + cond_resched(); first_iteration = false; goto again; diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index 94b376446042..9c1f160c5984 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -287,7 +287,7 @@ static inline int clear_extent_dirty(struct extent_io_tree *tree, u64 start, int convert_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, unsigned bits, unsigned clear_bits, - struct extent_state **cached_state, gfp_t mask); + struct extent_state **cached_state); static inline int set_extent_delalloc(struct extent_io_tree *tree, u64 start, u64 end, struct extent_state **cached_state) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 43885e51b882..98b93d9c6213 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -943,7 +943,7 @@ int btrfs_write_marked_extents(struct btrfs_root *root, err = convert_extent_bit(dirty_pages, start, end, EXTENT_NEED_WAIT, - mark, &cached_state, GFP_NOFS); + mark, &cached_state); /* * convert_extent_bit can return -ENOMEM, which is most of the * time a temporary error. So when it happens, ignore the error From 8ea416748bb04b7a778cb8d2fd5ec7fa51b9d521 Mon Sep 17 00:00:00 2001 From: Jeeja KP Date: Thu, 5 May 2016 11:19:18 +0530 Subject: [PATCH 078/393] ASoC: topology: Fix memory leak in widget creation name and sname allocated in widget create are not freed when creation is successful, so free them. Signed-off-by: Jeeja KP Signed-off-by: Vinod Koul Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 29ae3d3a0f8a..ee7f15aa46fc 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1481,6 +1481,8 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, widget->dobj.type = SND_SOC_DOBJ_WIDGET; widget->dobj.ops = tplg->ops; widget->dobj.index = tplg->index; + kfree(template.sname); + kfree(template.name); list_add(&widget->dobj.list, &tplg->comp->dobj_list); return 0; From 575b245d9047d190c8cbc44e2f0ef14897836292 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 26 Apr 2016 18:28:17 -0400 Subject: [PATCH 079/393] fujitsu-laptop: Use IS_ENABLED() instead of checking for built-in or module The IS_ENABLED() macro checks if a Kconfig symbol has been enabled either built-in or as a module, use that macro instead of open coding the same. Signed-off-by: Javier Martinez Canillas Acked-by: Jonathan Woithe Signed-off-by: Darren Hart --- drivers/platform/x86/fujitsu-laptop.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 1cb39d00d014..ce41bc34288d 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -69,7 +69,7 @@ #include #include #include -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#if IS_ENABLED(CONFIG_LEDS_CLASS) #include #endif #include @@ -100,7 +100,7 @@ /* FUNC interface - responses */ #define UNSUPPORTED_CMD 0x80000000 -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#if IS_ENABLED(CONFIG_LEDS_CLASS) /* FUNC interface - LED control */ #define FUNC_LED_OFF 0x1 #define FUNC_LED_ON 0x30001 @@ -182,7 +182,7 @@ static struct fujitsu_hotkey_t *fujitsu_hotkey; static void acpi_fujitsu_hotkey_notify(struct acpi_device *device, u32 event); -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#if IS_ENABLED(CONFIG_LEDS_CLASS) static enum led_brightness logolamp_get(struct led_classdev *cdev); static void logolamp_set(struct led_classdev *cdev, enum led_brightness brightness); @@ -261,7 +261,7 @@ static int call_fext_func(int cmd, int arg0, int arg1, int arg2) return value; } -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#if IS_ENABLED(CONFIG_LEDS_CLASS) /* LED class callbacks */ static void logolamp_set(struct led_classdev *cdev, @@ -903,7 +903,7 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) /* Suspect this is a keymap of the application panel, print it */ pr_info("BTNI: [0x%x]\n", call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0)); -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#if IS_ENABLED(CONFIG_LEDS_CLASS) if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { result = led_classdev_register(&fujitsu->pf_device->dev, &logolamp_led); @@ -963,7 +963,7 @@ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device) struct fujitsu_hotkey_t *fujitsu_hotkey = acpi_driver_data(device); struct input_dev *input = fujitsu_hotkey->input; -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#if IS_ENABLED(CONFIG_LEDS_CLASS) if (fujitsu_hotkey->logolamp_registered) led_classdev_unregister(&logolamp_led); From 775d054aba90a2c787d4c081d6369f1fddaae0f4 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 1 May 2016 22:11:59 +0200 Subject: [PATCH 080/393] intel_telemetry: Constify telemetry_core_ops structures The telemetry_core_ops structures are never modified, so declare them as const. Done with the help of Coccinelle. Signed-off-by: Julia Lawall Signed-off-by: Darren Hart --- arch/x86/include/asm/intel_telemetry.h | 2 +- drivers/platform/x86/intel_telemetry_core.c | 6 +++--- drivers/platform/x86/intel_telemetry_pltdrv.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/x86/include/asm/intel_telemetry.h b/arch/x86/include/asm/intel_telemetry.h index ed65fe701de5..85029b58d0cd 100644 --- a/arch/x86/include/asm/intel_telemetry.h +++ b/arch/x86/include/asm/intel_telemetry.h @@ -99,7 +99,7 @@ struct telemetry_core_ops { int (*reset_events)(void); }; -int telemetry_set_pltdata(struct telemetry_core_ops *ops, +int telemetry_set_pltdata(const struct telemetry_core_ops *ops, struct telemetry_plt_config *pltconfig); int telemetry_clear_pltdata(void); diff --git a/drivers/platform/x86/intel_telemetry_core.c b/drivers/platform/x86/intel_telemetry_core.c index a695a436a1c3..0d4c3808a6d8 100644 --- a/drivers/platform/x86/intel_telemetry_core.c +++ b/drivers/platform/x86/intel_telemetry_core.c @@ -25,7 +25,7 @@ struct telemetry_core_config { struct telemetry_plt_config *plt_config; - struct telemetry_core_ops *telem_ops; + const struct telemetry_core_ops *telem_ops; }; static struct telemetry_core_config telm_core_conf; @@ -95,7 +95,7 @@ static int telemetry_def_reset_events(void) return 0; } -static struct telemetry_core_ops telm_defpltops = { +static const struct telemetry_core_ops telm_defpltops = { .set_sampling_period = telemetry_def_set_sampling_period, .get_sampling_period = telemetry_def_get_sampling_period, .get_trace_verbosity = telemetry_def_get_trace_verbosity, @@ -332,7 +332,7 @@ EXPORT_SYMBOL_GPL(telemetry_set_trace_verbosity); * * Return: 0 success, < 0 for failure */ -int telemetry_set_pltdata(struct telemetry_core_ops *ops, +int telemetry_set_pltdata(const struct telemetry_core_ops *ops, struct telemetry_plt_config *pltconfig) { if (ops) diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c index 397119f83e82..1347da642d44 100644 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ b/drivers/platform/x86/intel_telemetry_pltdrv.c @@ -1081,7 +1081,7 @@ static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit, return ret; } -static struct telemetry_core_ops telm_pltops = { +static const struct telemetry_core_ops telm_pltops = { .get_trace_verbosity = telemetry_plt_get_trace_verbosity, .set_trace_verbosity = telemetry_plt_set_trace_verbosity, .set_sampling_period = telemetry_plt_set_sampling_period, From ddd9357f6017a8c61a20685feab5ce9eee635f6c Mon Sep 17 00:00:00 2001 From: Lawrence Yiu Date: Mon, 21 Mar 2016 01:44:22 -0700 Subject: [PATCH 081/393] sony-laptop: Avoid oops on module unload for older laptops Older VAIO laptops without the SN00 ACPI method will have the "handles" variable unset. Return early from sony_nc_function_cleanup when "handles" is null. Signed-off-by: Lawrence Yiu Acked-by: Mattia Dongili Signed-off-by: Darren Hart --- drivers/platform/x86/sony-laptop.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index e9caa347a9bf..1dba3598cfcb 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -1446,6 +1446,9 @@ static void sony_nc_function_cleanup(struct platform_device *pd) { unsigned int i, result, bitmask, handle; + if (!handles) + return; + /* get enabled events and disable them */ sony_nc_int_call(sony_nc_acpi_handle, "SN01", NULL, &bitmask); sony_nc_int_call(sony_nc_acpi_handle, "SN03", &bitmask, &result); From e5b7d71aa5b32180adec49a17c752e577c68f740 Mon Sep 17 00:00:00 2001 From: Andrea Adami Date: Fri, 6 May 2016 17:27:34 +0200 Subject: [PATCH 082/393] ASoC: pxa: Fix module autoload for platform drivers These platform drivers are lacking MODULE_ALIAS so module autoloading doesn't work. Tested on corgi and poodle with kernel 4.4. Signed-off-by: Andrea Adami Signed-off-by: Mark Brown --- sound/soc/pxa/brownstone.c | 1 + sound/soc/pxa/mioa701_wm9713.c | 1 + sound/soc/pxa/mmp-pcm.c | 1 + sound/soc/pxa/mmp-sspa.c | 1 + sound/soc/pxa/palm27x.c | 1 + sound/soc/pxa/pxa-ssp.c | 1 + sound/soc/pxa/pxa2xx-ac97.c | 1 + sound/soc/pxa/pxa2xx-pcm.c | 1 + 8 files changed, 8 insertions(+) diff --git a/sound/soc/pxa/brownstone.c b/sound/soc/pxa/brownstone.c index ec522e94b0e2..b6cb9950f05d 100644 --- a/sound/soc/pxa/brownstone.c +++ b/sound/soc/pxa/brownstone.c @@ -133,3 +133,4 @@ module_platform_driver(mmp_driver); MODULE_AUTHOR("Leo Yan "); MODULE_DESCRIPTION("ALSA SoC Brownstone"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:brownstone-audio"); diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c index 5c8f9db50a47..d1661fa6ee08 100644 --- a/sound/soc/pxa/mioa701_wm9713.c +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -207,3 +207,4 @@ module_platform_driver(mioa701_wm9713_driver); MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)"); MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mioa701-wm9713"); diff --git a/sound/soc/pxa/mmp-pcm.c b/sound/soc/pxa/mmp-pcm.c index 51e790d006f5..96df9b2d8fc4 100644 --- a/sound/soc/pxa/mmp-pcm.c +++ b/sound/soc/pxa/mmp-pcm.c @@ -248,3 +248,4 @@ module_platform_driver(mmp_pcm_driver); MODULE_AUTHOR("Leo Yan "); MODULE_DESCRIPTION("MMP Soc Audio DMA module"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mmp-pcm-audio"); diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c index eca60c29791a..ca8b23f8c525 100644 --- a/sound/soc/pxa/mmp-sspa.c +++ b/sound/soc/pxa/mmp-sspa.c @@ -482,3 +482,4 @@ module_platform_driver(asoc_mmp_sspa_driver); MODULE_AUTHOR("Leo Yan "); MODULE_DESCRIPTION("MMP SSPA SoC Interface"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mmp-sspa-dai"); diff --git a/sound/soc/pxa/palm27x.c b/sound/soc/pxa/palm27x.c index 4e74d9573f03..bcc81e920a67 100644 --- a/sound/soc/pxa/palm27x.c +++ b/sound/soc/pxa/palm27x.c @@ -161,3 +161,4 @@ module_platform_driver(palm27x_wm9712_driver); MODULE_AUTHOR("Marek Vasut "); MODULE_DESCRIPTION("ALSA SoC Palm T|X, T5 and LifeDrive"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:palm27x-asoc"); diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index da03fad1b9cd..3cad990dad2c 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -833,3 +833,4 @@ module_platform_driver(asoc_ssp_driver); MODULE_AUTHOR("Mark Brown "); MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa-ssp-dai"); diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index f3de615aacd7..9615e6de1306 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -287,3 +287,4 @@ module_platform_driver(pxa2xx_ac97_driver); MODULE_AUTHOR("Nicolas Pitre"); MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa2xx-ac97"); diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 9f390398d518..410d48b93031 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -117,3 +117,4 @@ module_platform_driver(pxa_pcm_driver); MODULE_AUTHOR("Nicolas Pitre"); MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa-pcm-audio"); From 7c3767115a04bc7aa87bdbd3352d1801d4bbeea4 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 9 May 2016 13:38:10 +0300 Subject: [PATCH 083/393] ASoC: simple-card: Add pm callbacks to platform driver Set snd_soc_pm_ops for the pm ops to make sure that the ASoC level of PM operations are going to happen. This is needed to get suspend/resume working correctly when the audio is using simple-card. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/generic/simple-card.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 2389ab47e25f..466492b7d4f5 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -643,6 +643,7 @@ MODULE_DEVICE_TABLE(of, asoc_simple_of_match); static struct platform_driver asoc_simple_card = { .driver = { .name = "asoc-simple-card", + .pm = &snd_soc_pm_ops, .of_match_table = asoc_simple_of_match, }, .probe = asoc_simple_card_probe, From a96e9783e05851d5f06da0ae7635aec55a228e3d Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 14 Apr 2016 18:18:50 -0700 Subject: [PATCH 084/393] target: make target db location configurable This commit adds the read-write attribute "dbroot", in the top-level CONFIGFS (core) target directory, normally /sys/kernel/config/target. This attribute defaults to "/var/target" but can be changed by writing a new pathname string to it. Changing this attribute is only allowed when no fabric drivers are loaded and the supplied value specifies an existing directory. Target modules that care about the target database root directory will be modified to use this attribute in a future commit. Signed-off-by: Lee Duncan Signed-off-by: Nicholas Bellinger --- drivers/target/target_core_configfs.c | 62 +++++++++++++++++++++++++++ drivers/target/target_core_internal.h | 6 +++ 2 files changed, 68 insertions(+) diff --git a/drivers/target/target_core_configfs.c b/drivers/target/target_core_configfs.c index d498533f09ee..73c23c75528a 100644 --- a/drivers/target/target_core_configfs.c +++ b/drivers/target/target_core_configfs.c @@ -99,6 +99,67 @@ static ssize_t target_core_item_version_show(struct config_item *item, CONFIGFS_ATTR_RO(target_core_item_, version); +char db_root[DB_ROOT_LEN] = DB_ROOT_DEFAULT; +static char db_root_stage[DB_ROOT_LEN]; + +static ssize_t target_core_item_dbroot_show(struct config_item *item, + char *page) +{ + return sprintf(page, "%s\n", db_root); +} + +static ssize_t target_core_item_dbroot_store(struct config_item *item, + const char *page, size_t count) +{ + ssize_t read_bytes; + struct file *fp; + + mutex_lock(&g_tf_lock); + if (!list_empty(&g_tf_list)) { + mutex_unlock(&g_tf_lock); + pr_err("db_root: cannot be changed: target drivers registered"); + return -EINVAL; + } + + if (count > (DB_ROOT_LEN - 1)) { + mutex_unlock(&g_tf_lock); + pr_err("db_root: count %d exceeds DB_ROOT_LEN-1: %u\n", + (int)count, DB_ROOT_LEN - 1); + return -EINVAL; + } + + read_bytes = snprintf(db_root_stage, DB_ROOT_LEN, "%s", page); + if (!read_bytes) { + mutex_unlock(&g_tf_lock); + return -EINVAL; + } + if (db_root_stage[read_bytes - 1] == '\n') + db_root_stage[read_bytes - 1] = '\0'; + + /* validate new db root before accepting it */ + fp = filp_open(db_root_stage, O_RDONLY, 0); + if (IS_ERR(fp)) { + mutex_unlock(&g_tf_lock); + pr_err("db_root: cannot open: %s\n", db_root_stage); + return -EINVAL; + } + if (!S_ISDIR(fp->f_inode->i_mode)) { + filp_close(fp, 0); + mutex_unlock(&g_tf_lock); + pr_err("db_root: not a directory: %s\n", db_root_stage); + return -EINVAL; + } + filp_close(fp, 0); + + strncpy(db_root, db_root_stage, read_bytes); + + mutex_unlock(&g_tf_lock); + + return read_bytes; +} + +CONFIGFS_ATTR(target_core_item_, dbroot); + static struct target_fabric_configfs *target_core_get_fabric( const char *name) { @@ -239,6 +300,7 @@ static struct configfs_group_operations target_core_fabric_group_ops = { */ static struct configfs_attribute *target_core_fabric_item_attrs[] = { &target_core_item_attr_version, + &target_core_item_attr_dbroot, NULL, }; diff --git a/drivers/target/target_core_internal.h b/drivers/target/target_core_internal.h index 86b4a8375628..fc91e85f54ba 100644 --- a/drivers/target/target_core_internal.h +++ b/drivers/target/target_core_internal.h @@ -155,4 +155,10 @@ void target_stat_setup_mappedlun_default_groups(struct se_lun_acl *); /* target_core_xcopy.c */ extern struct se_portal_group xcopy_pt_tpg; +/* target_core_configfs.c */ +#define DB_ROOT_LEN 4096 +#define DB_ROOT_DEFAULT "/var/target" + +extern char db_root[]; + #endif /* TARGET_CORE_INTERNAL_H */ From fdddf932269a75c3dd1c68d82b9a0fbc1821a2a8 Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 14 Apr 2016 18:18:51 -0700 Subject: [PATCH 085/393] target: use new "dbroot" target attribute This commit updates the target core ALUA and PR modules to use the new "dbroot" attribute instead of assuming the target database is in "/var/target". Signed-off-by: Lee Duncan Reviewed-by: Hannes Reinecke Signed-off-by: Nicholas Bellinger --- drivers/target/target_core_alua.c | 6 +++--- drivers/target/target_core_pr.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/target/target_core_alua.c b/drivers/target/target_core_alua.c index 49aba4a31747..4c82bbe19003 100644 --- a/drivers/target/target_core_alua.c +++ b/drivers/target/target_core_alua.c @@ -932,7 +932,7 @@ static int core_alua_update_tpg_primary_metadata( tg_pt_gp->tg_pt_gp_alua_access_status); snprintf(path, ALUA_METADATA_PATH_LEN, - "/var/target/alua/tpgs_%s/%s", &wwn->unit_serial[0], + "%s/alua/tpgs_%s/%s", db_root, &wwn->unit_serial[0], config_item_name(&tg_pt_gp->tg_pt_gp_group.cg_item)); rc = core_alua_write_tpg_metadata(path, md_buf, len); @@ -1275,8 +1275,8 @@ static int core_alua_update_tpg_secondary_metadata(struct se_lun *lun) atomic_read(&lun->lun_tg_pt_secondary_offline), lun->lun_tg_pt_secondary_stat); - snprintf(path, ALUA_METADATA_PATH_LEN, "/var/target/alua/%s/%s/lun_%llu", - se_tpg->se_tpg_tfo->get_fabric_name(), wwn, + snprintf(path, ALUA_METADATA_PATH_LEN, "%s/alua/%s/%s/lun_%llu", + db_root, se_tpg->se_tpg_tfo->get_fabric_name(), wwn, lun->unpacked_lun); rc = core_alua_write_tpg_metadata(path, md_buf, len); diff --git a/drivers/target/target_core_pr.c b/drivers/target/target_core_pr.c index b1795735eafc..47463c99c318 100644 --- a/drivers/target/target_core_pr.c +++ b/drivers/target/target_core_pr.c @@ -1985,7 +1985,7 @@ static int __core_scsi3_write_aptpl_to_file( return -EMSGSIZE; } - snprintf(path, 512, "/var/target/pr/aptpl_%s", &wwn->unit_serial[0]); + snprintf(path, 512, "%s/pr/aptpl_%s", db_root, &wwn->unit_serial[0]); file = filp_open(path, flags, 0600); if (IS_ERR(file)) { pr_err("filp_open(%s) for APTPL metadata" From 2854bb23cd5bef39fd845b7bdfac7799438bb920 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:08 +0530 Subject: [PATCH 086/393] iscsi-target: add int (*iscsit_xmit_pdu)() Add int (*iscsit_xmit_pdu)() to struct iscsit_transport, iscsi-target uses this callback to transmit an iSCSI PDU. cxgbit.ko needs this callback to avoid duplicating iscsit_immediate_queue() and iscsit_response_queue() code. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 546 +++++++++---------------- include/target/iscsi/iscsi_transport.h | 2 + 2 files changed, 197 insertions(+), 351 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 961202f4e9aa..0724c8355e29 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -499,6 +499,168 @@ static void iscsit_aborted_task(struct iscsi_conn *conn, struct iscsi_cmd *cmd) __iscsit_free_cmd(cmd, scsi_cmd, true); } +static void iscsit_do_crypto_hash_buf(struct ahash_request *, const void *, + u32, u32, u8 *, u8 *); +static void iscsit_tx_thread_wait_for_tcp(struct iscsi_conn *); + +static int +iscsit_xmit_nondatain_pdu(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + const void *data_buf, u32 data_buf_len) +{ + struct iscsi_hdr *hdr = (struct iscsi_hdr *)cmd->pdu; + struct kvec *iov; + u32 niov = 0, tx_size = ISCSI_HDR_LEN; + int ret; + + iov = &cmd->iov_misc[0]; + iov[niov].iov_base = cmd->pdu; + iov[niov++].iov_len = ISCSI_HDR_LEN; + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, + ISCSI_HDR_LEN, 0, NULL, + (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32C HeaderDigest" + " to opcode 0x%x 0x%08x\n", + hdr->opcode, *header_digest); + } + + if (data_buf_len) { + u32 padding = ((-data_buf_len) & 3); + + iov[niov].iov_base = (void *)data_buf; + iov[niov++].iov_len = data_buf_len; + tx_size += data_buf_len; + + if (padding != 0) { + iov[niov].iov_base = &cmd->pad_bytes; + iov[niov++].iov_len = padding; + tx_size += padding; + pr_debug("Attaching %u additional" + " padding bytes.\n", padding); + } + + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(conn->conn_tx_hash, + data_buf, data_buf_len, + padding, + (u8 *)&cmd->pad_bytes, + (u8 *)&cmd->data_crc); + + iov[niov].iov_base = &cmd->data_crc; + iov[niov++].iov_len = ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attached DataDigest for %u" + " bytes opcode 0x%x, CRC 0x%08x\n", + data_buf_len, hdr->opcode, cmd->data_crc); + } + } + + cmd->iov_misc_count = niov; + cmd->tx_size = tx_size; + + ret = iscsit_send_tx_data(cmd, conn, 1); + if (ret < 0) { + iscsit_tx_thread_wait_for_tcp(conn); + return ret; + } + + return 0; +} + +static int iscsit_map_iovec(struct iscsi_cmd *, struct kvec *, u32, u32); +static void iscsit_unmap_iovec(struct iscsi_cmd *); +static u32 iscsit_do_crypto_hash_sg(struct ahash_request *, struct iscsi_cmd *, + u32, u32, u32, u8 *); +static int +iscsit_xmit_datain_pdu(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + const struct iscsi_datain *datain) +{ + struct kvec *iov; + u32 iov_count = 0, tx_size = 0; + int ret, iov_ret; + + iov = &cmd->iov_data[0]; + iov[iov_count].iov_base = cmd->pdu; + iov[iov_count++].iov_len = ISCSI_HDR_LEN; + tx_size += ISCSI_HDR_LEN; + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(conn->conn_tx_hash, cmd->pdu, + ISCSI_HDR_LEN, 0, NULL, + (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + + pr_debug("Attaching CRC32 HeaderDigest for DataIN PDU 0x%08x\n", + *header_digest); + } + + iov_ret = iscsit_map_iovec(cmd, &cmd->iov_data[1], + datain->offset, datain->length); + if (iov_ret < 0) + return -1; + + iov_count += iov_ret; + tx_size += datain->length; + + cmd->padding = ((-datain->length) & 3); + if (cmd->padding) { + iov[iov_count].iov_base = cmd->pad_bytes; + iov[iov_count++].iov_len = cmd->padding; + tx_size += cmd->padding; + + pr_debug("Attaching %u padding bytes\n", cmd->padding); + } + + if (conn->conn_ops->DataDigest) { + cmd->data_crc = iscsit_do_crypto_hash_sg(conn->conn_tx_hash, + cmd, datain->offset, + datain->length, + cmd->padding, + cmd->pad_bytes); + + iov[iov_count].iov_base = &cmd->data_crc; + iov[iov_count++].iov_len = ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + + pr_debug("Attached CRC32C DataDigest %d bytes, crc 0x%08x\n", + datain->length + cmd->padding, cmd->data_crc); + } + + cmd->iov_data_count = iov_count; + cmd->tx_size = tx_size; + + ret = iscsit_fe_sendpage_sg(cmd, conn); + + iscsit_unmap_iovec(cmd); + + if (ret < 0) { + iscsit_tx_thread_wait_for_tcp(conn); + return ret; + } + + return 0; +} + +static int iscsit_xmit_pdu(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_datain_req *dr, const void *buf, + u32 buf_len) +{ + if (dr) + return iscsit_xmit_datain_pdu(conn, cmd, buf); + else + return iscsit_xmit_nondatain_pdu(conn, cmd, buf, buf_len); +} + static enum target_prot_op iscsit_get_sup_prot_ops(struct iscsi_conn *conn) { return TARGET_PROT_NORMAL; @@ -519,6 +681,7 @@ static struct iscsit_transport iscsi_target_transport = { .iscsit_queue_data_in = iscsit_queue_rsp, .iscsit_queue_status = iscsit_queue_rsp, .iscsit_aborted_task = iscsit_aborted_task, + .iscsit_xmit_pdu = iscsit_xmit_pdu, .iscsit_get_sup_prot_ops = iscsit_get_sup_prot_ops, }; @@ -2534,7 +2697,6 @@ static int iscsit_send_conn_drop_async_message( { struct iscsi_async *hdr; - cmd->tx_size = ISCSI_HDR_LEN; cmd->iscsi_opcode = ISCSI_OP_ASYNC_EVENT; hdr = (struct iscsi_async *) cmd->pdu; @@ -2552,25 +2714,11 @@ static int iscsit_send_conn_drop_async_message( hdr->param2 = cpu_to_be16(conn->sess->sess_ops->DefaultTime2Wait); hdr->param3 = cpu_to_be16(conn->sess->sess_ops->DefaultTime2Retain); - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - cmd->tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32C HeaderDigest to" - " Async Message 0x%08x\n", *header_digest); - } - - cmd->iov_misc[0].iov_base = cmd->pdu; - cmd->iov_misc[0].iov_len = cmd->tx_size; - cmd->iov_misc_count = 1; - pr_debug("Sending Connection Dropped Async Message StatSN:" " 0x%08x, for CID: %hu on CID: %hu\n", cmd->stat_sn, cmd->logout_cid, conn->cid); - return 0; + + return conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, NULL, 0); } static void iscsit_tx_thread_wait_for_tcp(struct iscsi_conn *conn) @@ -2633,9 +2781,7 @@ static int iscsit_send_datain(struct iscsi_cmd *cmd, struct iscsi_conn *conn) struct iscsi_data_rsp *hdr = (struct iscsi_data_rsp *)&cmd->pdu[0]; struct iscsi_datain datain; struct iscsi_datain_req *dr; - struct kvec *iov; - u32 iov_count = 0, tx_size = 0; - int eodr = 0, ret, iov_ret; + int eodr = 0, ret; bool set_statsn = false; memset(&datain, 0, sizeof(struct iscsi_datain)); @@ -2677,64 +2823,9 @@ static int iscsit_send_datain(struct iscsi_cmd *cmd, struct iscsi_conn *conn) iscsit_build_datain_pdu(cmd, conn, &datain, hdr, set_statsn); - iov = &cmd->iov_data[0]; - iov[iov_count].iov_base = cmd->pdu; - iov[iov_count++].iov_len = ISCSI_HDR_LEN; - tx_size += ISCSI_HDR_LEN; - - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, cmd->pdu, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - iov[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - - pr_debug("Attaching CRC32 HeaderDigest" - " for DataIN PDU 0x%08x\n", *header_digest); - } - - iov_ret = iscsit_map_iovec(cmd, &cmd->iov_data[1], - datain.offset, datain.length); - if (iov_ret < 0) - return -1; - - iov_count += iov_ret; - tx_size += datain.length; - - cmd->padding = ((-datain.length) & 3); - if (cmd->padding) { - iov[iov_count].iov_base = cmd->pad_bytes; - iov[iov_count++].iov_len = cmd->padding; - tx_size += cmd->padding; - - pr_debug("Attaching %u padding bytes\n", - cmd->padding); - } - if (conn->conn_ops->DataDigest) { - cmd->data_crc = iscsit_do_crypto_hash_sg(conn->conn_tx_hash, cmd, - datain.offset, datain.length, cmd->padding, cmd->pad_bytes); - - iov[iov_count].iov_base = &cmd->data_crc; - iov[iov_count++].iov_len = ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - - pr_debug("Attached CRC32C DataDigest %d bytes, crc" - " 0x%08x\n", datain.length+cmd->padding, cmd->data_crc); - } - - cmd->iov_data_count = iov_count; - cmd->tx_size = tx_size; - - ret = iscsit_fe_sendpage_sg(cmd, conn); - - iscsit_unmap_iovec(cmd); - - if (ret < 0) { - iscsit_tx_thread_wait_for_tcp(conn); + ret = conn->conn_transport->iscsit_xmit_pdu(conn, cmd, dr, &datain, 0); + if (ret < 0) return ret; - } if (dr->dr_complete) { eodr = (cmd->se_cmd.se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) ? @@ -2843,34 +2934,14 @@ EXPORT_SYMBOL(iscsit_build_logout_rsp); static int iscsit_send_logout(struct iscsi_cmd *cmd, struct iscsi_conn *conn) { - struct kvec *iov; - int niov = 0, tx_size, rc; + int rc; rc = iscsit_build_logout_rsp(cmd, conn, (struct iscsi_logout_rsp *)&cmd->pdu[0]); if (rc < 0) return rc; - tx_size = ISCSI_HDR_LEN; - iov = &cmd->iov_misc[0]; - iov[niov].iov_base = cmd->pdu; - iov[niov++].iov_len = ISCSI_HDR_LEN; - - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, &cmd->pdu[0], - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - iov[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32C HeaderDigest to" - " Logout Response 0x%08x\n", *header_digest); - } - cmd->iov_misc_count = niov; - cmd->tx_size = tx_size; - - return 0; + return conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, NULL, 0); } void @@ -2910,34 +2981,16 @@ static int iscsit_send_unsolicited_nopin( int want_response) { struct iscsi_nopin *hdr = (struct iscsi_nopin *)&cmd->pdu[0]; - int tx_size = ISCSI_HDR_LEN, ret; + int ret; iscsit_build_nopin_rsp(cmd, conn, hdr, false); - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32C HeaderDigest to" - " NopIN 0x%08x\n", *header_digest); - } - - cmd->iov_misc[0].iov_base = cmd->pdu; - cmd->iov_misc[0].iov_len = tx_size; - cmd->iov_misc_count = 1; - cmd->tx_size = tx_size; - pr_debug("Sending Unsolicited NOPIN TTT: 0x%08x StatSN:" " 0x%08x CID: %hu\n", hdr->ttt, cmd->stat_sn, conn->cid); - ret = iscsit_send_tx_data(cmd, conn, 1); - if (ret < 0) { - iscsit_tx_thread_wait_for_tcp(conn); + ret = conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, NULL, 0); + if (ret < 0) return ret; - } spin_lock_bh(&cmd->istate_lock); cmd->i_state = want_response ? @@ -2951,75 +3004,24 @@ static int iscsit_send_nopin(struct iscsi_cmd *cmd, struct iscsi_conn *conn) { struct iscsi_nopin *hdr = (struct iscsi_nopin *)&cmd->pdu[0]; - struct kvec *iov; - u32 padding = 0; - int niov = 0, tx_size; iscsit_build_nopin_rsp(cmd, conn, hdr, true); - tx_size = ISCSI_HDR_LEN; - iov = &cmd->iov_misc[0]; - iov[niov].iov_base = cmd->pdu; - iov[niov++].iov_len = ISCSI_HDR_LEN; - - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - iov[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32C HeaderDigest" - " to NopIn 0x%08x\n", *header_digest); - } - /* * NOPOUT Ping Data is attached to struct iscsi_cmd->buf_ptr. * NOPOUT DataSegmentLength is at struct iscsi_cmd->buf_ptr_size. */ - if (cmd->buf_ptr_size) { - iov[niov].iov_base = cmd->buf_ptr; - iov[niov++].iov_len = cmd->buf_ptr_size; - tx_size += cmd->buf_ptr_size; + pr_debug("Echoing back %u bytes of ping data.\n", cmd->buf_ptr_size); - pr_debug("Echoing back %u bytes of ping" - " data.\n", cmd->buf_ptr_size); - - padding = ((-cmd->buf_ptr_size) & 3); - if (padding != 0) { - iov[niov].iov_base = &cmd->pad_bytes; - iov[niov++].iov_len = padding; - tx_size += padding; - pr_debug("Attaching %u additional" - " padding bytes.\n", padding); - } - if (conn->conn_ops->DataDigest) { - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, - cmd->buf_ptr, cmd->buf_ptr_size, - padding, (u8 *)&cmd->pad_bytes, - (u8 *)&cmd->data_crc); - - iov[niov].iov_base = &cmd->data_crc; - iov[niov++].iov_len = ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attached DataDigest for %u" - " bytes of ping data, CRC 0x%08x\n", - cmd->buf_ptr_size, cmd->data_crc); - } - } - - cmd->iov_misc_count = niov; - cmd->tx_size = tx_size; - - return 0; + return conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, + cmd->buf_ptr, + cmd->buf_ptr_size); } static int iscsit_send_r2t( struct iscsi_cmd *cmd, struct iscsi_conn *conn) { - int tx_size = 0; struct iscsi_r2t *r2t; struct iscsi_r2t_rsp *hdr; int ret; @@ -3044,38 +3046,18 @@ static int iscsit_send_r2t( hdr->data_offset = cpu_to_be32(r2t->offset); hdr->data_length = cpu_to_be32(r2t->xfer_len); - cmd->iov_misc[0].iov_base = cmd->pdu; - cmd->iov_misc[0].iov_len = ISCSI_HDR_LEN; - tx_size += ISCSI_HDR_LEN; - - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - cmd->iov_misc[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32 HeaderDigest for R2T" - " PDU 0x%08x\n", *header_digest); - } - pr_debug("Built %sR2T, ITT: 0x%08x, TTT: 0x%08x, StatSN:" " 0x%08x, R2TSN: 0x%08x, Offset: %u, DDTL: %u, CID: %hu\n", (!r2t->recovery_r2t) ? "" : "Recovery ", cmd->init_task_tag, r2t->targ_xfer_tag, ntohl(hdr->statsn), r2t->r2t_sn, r2t->offset, r2t->xfer_len, conn->cid); - cmd->iov_misc_count = 1; - cmd->tx_size = tx_size; - spin_lock_bh(&cmd->r2t_lock); r2t->sent_r2t = 1; spin_unlock_bh(&cmd->r2t_lock); - ret = iscsit_send_tx_data(cmd, conn, 1); + ret = conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, NULL, 0); if (ret < 0) { - iscsit_tx_thread_wait_for_tcp(conn); return ret; } @@ -3204,18 +3186,12 @@ EXPORT_SYMBOL(iscsit_build_rsp_pdu); static int iscsit_send_response(struct iscsi_cmd *cmd, struct iscsi_conn *conn) { struct iscsi_scsi_rsp *hdr = (struct iscsi_scsi_rsp *)&cmd->pdu[0]; - struct kvec *iov; - u32 padding = 0, tx_size = 0; - int iov_count = 0; bool inc_stat_sn = (cmd->i_state == ISTATE_SEND_STATUS); + void *data_buf = NULL; + u32 padding = 0, data_buf_len = 0; iscsit_build_rsp_pdu(cmd, conn, inc_stat_sn, hdr); - iov = &cmd->iov_misc[0]; - iov[iov_count].iov_base = cmd->pdu; - iov[iov_count++].iov_len = ISCSI_HDR_LEN; - tx_size += ISCSI_HDR_LEN; - /* * Attach SENSE DATA payload to iSCSI Response PDU */ @@ -3227,56 +3203,23 @@ static int iscsit_send_response(struct iscsi_cmd *cmd, struct iscsi_conn *conn) padding = -(cmd->se_cmd.scsi_sense_length) & 3; hton24(hdr->dlength, (u32)cmd->se_cmd.scsi_sense_length); - iov[iov_count].iov_base = cmd->sense_buffer; - iov[iov_count++].iov_len = - (cmd->se_cmd.scsi_sense_length + padding); - tx_size += cmd->se_cmd.scsi_sense_length; + data_buf = cmd->sense_buffer; + data_buf_len = cmd->se_cmd.scsi_sense_length + padding; if (padding) { memset(cmd->sense_buffer + cmd->se_cmd.scsi_sense_length, 0, padding); - tx_size += padding; pr_debug("Adding %u bytes of padding to" " SENSE.\n", padding); } - if (conn->conn_ops->DataDigest) { - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, - cmd->sense_buffer, - (cmd->se_cmd.scsi_sense_length + padding), - 0, NULL, (u8 *)&cmd->data_crc); - - iov[iov_count].iov_base = &cmd->data_crc; - iov[iov_count++].iov_len = ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - - pr_debug("Attaching CRC32 DataDigest for" - " SENSE, %u bytes CRC 0x%08x\n", - (cmd->se_cmd.scsi_sense_length + padding), - cmd->data_crc); - } - pr_debug("Attaching SENSE DATA: %u bytes to iSCSI" " Response PDU\n", cmd->se_cmd.scsi_sense_length); } - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, cmd->pdu, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - iov[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32 HeaderDigest for Response" - " PDU 0x%08x\n", *header_digest); - } - - cmd->iov_misc_count = iov_count; - cmd->tx_size = tx_size; - - return 0; + return conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, data_buf, + data_buf_len); } static u8 iscsit_convert_tcm_tmr_rsp(struct se_tmr_req *se_tmr) @@ -3323,30 +3266,10 @@ static int iscsit_send_task_mgt_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn) { struct iscsi_tm_rsp *hdr = (struct iscsi_tm_rsp *)&cmd->pdu[0]; - u32 tx_size = 0; iscsit_build_task_mgt_rsp(cmd, conn, hdr); - cmd->iov_misc[0].iov_base = cmd->pdu; - cmd->iov_misc[0].iov_len = ISCSI_HDR_LEN; - tx_size += ISCSI_HDR_LEN; - - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - cmd->iov_misc[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32 HeaderDigest for Task" - " Mgmt Response PDU 0x%08x\n", *header_digest); - } - - cmd->iov_misc_count = 1; - cmd->tx_size = tx_size; - - return 0; + return conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, NULL, 0); } static bool iscsit_check_inaddr_any(struct iscsi_np *np) @@ -3583,53 +3506,15 @@ static int iscsit_send_text_rsp( struct iscsi_conn *conn) { struct iscsi_text_rsp *hdr = (struct iscsi_text_rsp *)cmd->pdu; - struct kvec *iov; - u32 tx_size = 0; - int text_length, iov_count = 0, rc; + int text_length; - rc = iscsit_build_text_rsp(cmd, conn, hdr, ISCSI_TCP); - if (rc < 0) - return rc; + text_length = iscsit_build_text_rsp(cmd, conn, hdr, ISCSI_TCP); + if (text_length < 0) + return text_length; - text_length = rc; - iov = &cmd->iov_misc[0]; - iov[iov_count].iov_base = cmd->pdu; - iov[iov_count++].iov_len = ISCSI_HDR_LEN; - iov[iov_count].iov_base = cmd->buf_ptr; - iov[iov_count++].iov_len = text_length; - - tx_size += (ISCSI_HDR_LEN + text_length); - - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - iov[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32 HeaderDigest for" - " Text Response PDU 0x%08x\n", *header_digest); - } - - if (conn->conn_ops->DataDigest) { - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, - cmd->buf_ptr, text_length, - 0, NULL, (u8 *)&cmd->data_crc); - - iov[iov_count].iov_base = &cmd->data_crc; - iov[iov_count++].iov_len = ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - - pr_debug("Attaching DataDigest for %u bytes of text" - " data, CRC 0x%08x\n", text_length, - cmd->data_crc); - } - - cmd->iov_misc_count = iov_count; - cmd->tx_size = tx_size; - - return 0; + return conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, + cmd->buf_ptr, + text_length); } void @@ -3654,49 +3539,15 @@ static int iscsit_send_reject( struct iscsi_conn *conn) { struct iscsi_reject *hdr = (struct iscsi_reject *)&cmd->pdu[0]; - struct kvec *iov; - u32 iov_count = 0, tx_size; iscsit_build_reject(cmd, conn, hdr); - iov = &cmd->iov_misc[0]; - iov[iov_count].iov_base = cmd->pdu; - iov[iov_count++].iov_len = ISCSI_HDR_LEN; - iov[iov_count].iov_base = cmd->buf_ptr; - iov[iov_count++].iov_len = ISCSI_HDR_LEN; - - tx_size = (ISCSI_HDR_LEN + ISCSI_HDR_LEN); - - if (conn->conn_ops->HeaderDigest) { - u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; - - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, hdr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); - - iov[0].iov_len += ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32 HeaderDigest for" - " REJECT PDU 0x%08x\n", *header_digest); - } - - if (conn->conn_ops->DataDigest) { - iscsit_do_crypto_hash_buf(conn->conn_tx_hash, cmd->buf_ptr, - ISCSI_HDR_LEN, 0, NULL, (u8 *)&cmd->data_crc); - - iov[iov_count].iov_base = &cmd->data_crc; - iov[iov_count++].iov_len = ISCSI_CRC_LEN; - tx_size += ISCSI_CRC_LEN; - pr_debug("Attaching CRC32 DataDigest for REJECT" - " PDU 0x%08x\n", cmd->data_crc); - } - - cmd->iov_misc_count = iov_count; - cmd->tx_size = tx_size; - pr_debug("Built Reject PDU StatSN: 0x%08x, Reason: 0x%02x," " CID: %hu\n", ntohl(hdr->statsn), hdr->reason, conn->cid); - return 0; + return conn->conn_transport->iscsit_xmit_pdu(conn, cmd, NULL, + cmd->buf_ptr, + ISCSI_HDR_LEN); } void iscsit_thread_get_cpumask(struct iscsi_conn *conn) @@ -3889,13 +3740,6 @@ iscsit_response_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) if (ret < 0) goto err; - if (iscsit_send_tx_data(cmd, conn, 1) < 0) { - iscsit_tx_thread_wait_for_tcp(conn); - iscsit_unmap_iovec(cmd); - goto err; - } - iscsit_unmap_iovec(cmd); - switch (state) { case ISTATE_SEND_LOGOUTRSP: if (!iscsit_logout_post_handler(cmd, conn)) diff --git a/include/target/iscsi/iscsi_transport.h b/include/target/iscsi/iscsi_transport.h index 90e37faa2ede..24b613a09aee 100644 --- a/include/target/iscsi/iscsi_transport.h +++ b/include/target/iscsi/iscsi_transport.h @@ -22,6 +22,8 @@ struct iscsit_transport { int (*iscsit_queue_data_in)(struct iscsi_conn *, struct iscsi_cmd *); int (*iscsit_queue_status)(struct iscsi_conn *, struct iscsi_cmd *); void (*iscsit_aborted_task)(struct iscsi_conn *, struct iscsi_cmd *); + int (*iscsit_xmit_pdu)(struct iscsi_conn *, struct iscsi_cmd *, + struct iscsi_datain_req *, const void *, u32); enum target_prot_op (*iscsit_get_sup_prot_ops)(struct iscsi_conn *); }; From 7ec811a8e9c3b8289a863d0dac717e08a5093403 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:09 +0530 Subject: [PATCH 087/393] iscsi-target: add void (*iscsit_release_cmd)() Add void (*iscsit_release_cmd)() to struct iscsit_transport, iscsi-target uses this callback to release transport driver resources associated with an iSCSI cmd. cxgbit.ko needs this callback to release DDP resource and sg page in case of PASSTHROUGH_SG_TO_MEM_NOALLOC. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target_util.c | 3 +++ include/target/iscsi/iscsi_transport.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c index 428b0d9e3dba..a5330171ebe5 100644 --- a/drivers/target/iscsi/iscsi_target_util.c +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -725,6 +725,9 @@ void __iscsit_free_cmd(struct iscsi_cmd *cmd, bool scsi_cmd, iscsit_remove_cmd_from_immediate_queue(cmd, conn); iscsit_remove_cmd_from_response_queue(cmd, conn); } + + if (conn && conn->conn_transport->iscsit_release_cmd) + conn->conn_transport->iscsit_release_cmd(conn, cmd); } void iscsit_free_cmd(struct iscsi_cmd *cmd, bool shutdown) diff --git a/include/target/iscsi/iscsi_transport.h b/include/target/iscsi/iscsi_transport.h index 24b613a09aee..3b0053efdfba 100644 --- a/include/target/iscsi/iscsi_transport.h +++ b/include/target/iscsi/iscsi_transport.h @@ -24,6 +24,7 @@ struct iscsit_transport { void (*iscsit_aborted_task)(struct iscsi_conn *, struct iscsi_cmd *); int (*iscsit_xmit_pdu)(struct iscsi_conn *, struct iscsi_cmd *, struct iscsi_datain_req *, const void *, u32); + void (*iscsit_release_cmd)(struct iscsi_conn *, struct iscsi_cmd *); enum target_prot_op (*iscsit_get_sup_prot_ops)(struct iscsi_conn *); }; From 187e84f7c2e4fa0028241c253c24658ef658e5d3 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:10 +0530 Subject: [PATCH 088/393] iscsi-target: add void (*iscsit_get_rx_pdu)() Add void (*iscsit_get_rx_pdu)() to struct iscsit_transport, iscsi-target uses this callback to receive and process Rx iSCSI PDUs. cxgbit.ko needs this callback to reuse iscsi-target Rx thread. Signed-off-by: Varun Prakash Acked-by: Sagi Grimberg Signed-off-by: Nicholas Bellinger --- drivers/infiniband/ulp/isert/ib_isert.c | 10 ++++++++++ drivers/target/iscsi/iscsi_target.c | 10 ++-------- include/target/iscsi/iscsi_transport.h | 1 + 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c index 411e4464ca23..9118d7c04a71 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.c +++ b/drivers/infiniband/ulp/isert/ib_isert.c @@ -3273,6 +3273,15 @@ static void isert_free_conn(struct iscsi_conn *conn) isert_put_conn(isert_conn); } +static void isert_get_rx_pdu(struct iscsi_conn *conn) +{ + struct completion comp; + + init_completion(&comp); + + wait_for_completion_interruptible(&comp); +} + static struct iscsit_transport iser_target_transport = { .name = "IB/iSER", .transport_type = ISCSI_INFINIBAND, @@ -3291,6 +3300,7 @@ static struct iscsit_transport iser_target_transport = { .iscsit_queue_data_in = isert_put_datain, .iscsit_queue_status = isert_put_response, .iscsit_aborted_task = isert_aborted_task, + .iscsit_get_rx_pdu = isert_get_rx_pdu, .iscsit_get_sup_prot_ops = isert_get_sup_prot_ops, }; diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 0724c8355e29..9fa9f0b3a0f4 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -3951,14 +3951,8 @@ int iscsi_target_rx_thread(void *arg) if (rc < 0 || iscsi_target_check_conn_state(conn)) return 0; - if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) { - struct completion comp; - - init_completion(&comp); - rc = wait_for_completion_interruptible(&comp); - if (rc < 0) - goto transport_err; - + if (conn->conn_transport->iscsit_get_rx_pdu) { + conn->conn_transport->iscsit_get_rx_pdu(conn); goto transport_err; } diff --git a/include/target/iscsi/iscsi_transport.h b/include/target/iscsi/iscsi_transport.h index 3b0053efdfba..ccc68a0334c2 100644 --- a/include/target/iscsi/iscsi_transport.h +++ b/include/target/iscsi/iscsi_transport.h @@ -25,6 +25,7 @@ struct iscsit_transport { int (*iscsit_xmit_pdu)(struct iscsi_conn *, struct iscsi_cmd *, struct iscsi_datain_req *, const void *, u32); void (*iscsit_release_cmd)(struct iscsi_conn *, struct iscsi_cmd *); + void (*iscsit_get_rx_pdu)(struct iscsi_conn *); enum target_prot_op (*iscsit_get_sup_prot_ops)(struct iscsi_conn *); }; From e8205cca0abd126418c18030fb8c9ef272080d70 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:11 +0530 Subject: [PATCH 089/393] iscsi-target: split iscsi_target_rx_thread() split iscsi_target_rx_thread() into two parts, 1. iscsi_target_rx_thread() is common to all transport drivers, it will call Rx function registered by transport driver. 2. iscsit_get_rx_pdu() is Rx function for ISCSI_TCP transport. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 59 ++++++++++++++++------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 9fa9f0b3a0f4..a827b7afe42c 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -480,6 +480,7 @@ int iscsit_del_np(struct iscsi_np *np) static int iscsit_immediate_queue(struct iscsi_conn *, struct iscsi_cmd *, int); static int iscsit_response_queue(struct iscsi_conn *, struct iscsi_cmd *, int); +static void iscsit_get_rx_pdu(struct iscsi_conn *); static int iscsit_queue_rsp(struct iscsi_conn *conn, struct iscsi_cmd *cmd) { @@ -682,6 +683,7 @@ static struct iscsit_transport iscsi_target_transport = { .iscsit_queue_status = iscsit_queue_rsp, .iscsit_aborted_task = iscsit_aborted_task, .iscsit_xmit_pdu = iscsit_xmit_pdu, + .iscsit_get_rx_pdu = iscsit_get_rx_pdu, .iscsit_get_sup_prot_ops = iscsit_get_sup_prot_ops, }; @@ -3931,30 +3933,12 @@ static bool iscsi_target_check_conn_state(struct iscsi_conn *conn) return ret; } -int iscsi_target_rx_thread(void *arg) +static void iscsit_get_rx_pdu(struct iscsi_conn *conn) { - int ret, rc; + int ret; u8 buffer[ISCSI_HDR_LEN], opcode; u32 checksum = 0, digest = 0; - struct iscsi_conn *conn = arg; struct kvec iov; - /* - * Allow ourselves to be interrupted by SIGINT so that a - * connection recovery / failure event can be triggered externally. - */ - allow_signal(SIGINT); - /* - * Wait for iscsi_post_login_handler() to complete before allowing - * incoming iscsi/tcp socket I/O, and/or failing the connection. - */ - rc = wait_for_completion_interruptible(&conn->rx_login_comp); - if (rc < 0 || iscsi_target_check_conn_state(conn)) - return 0; - - if (conn->conn_transport->iscsit_get_rx_pdu) { - conn->conn_transport->iscsit_get_rx_pdu(conn); - goto transport_err; - } while (!kthread_should_stop()) { /* @@ -3972,7 +3956,7 @@ int iscsi_target_rx_thread(void *arg) ret = rx_data(conn, &iov, 1, ISCSI_HDR_LEN); if (ret != ISCSI_HDR_LEN) { iscsit_rx_thread_wait_for_tcp(conn); - goto transport_err; + return; } if (conn->conn_ops->HeaderDigest) { @@ -3982,7 +3966,7 @@ int iscsi_target_rx_thread(void *arg) ret = rx_data(conn, &iov, 1, ISCSI_CRC_LEN); if (ret != ISCSI_CRC_LEN) { iscsit_rx_thread_wait_for_tcp(conn); - goto transport_err; + return; } iscsit_do_crypto_hash_buf(conn->conn_rx_hash, @@ -4006,7 +3990,7 @@ int iscsi_target_rx_thread(void *arg) } if (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT) - goto transport_err; + return; opcode = buffer[0] & ISCSI_OPCODE_MASK; @@ -4017,15 +4001,38 @@ int iscsi_target_rx_thread(void *arg) " while in Discovery Session, rejecting.\n", opcode); iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, buffer); - goto transport_err; + return; } ret = iscsi_target_rx_opcode(conn, buffer); if (ret < 0) - goto transport_err; + return; } +} + +int iscsi_target_rx_thread(void *arg) +{ + int rc; + struct iscsi_conn *conn = arg; + + /* + * Allow ourselves to be interrupted by SIGINT so that a + * connection recovery / failure event can be triggered externally. + */ + allow_signal(SIGINT); + /* + * Wait for iscsi_post_login_handler() to complete before allowing + * incoming iscsi/tcp socket I/O, and/or failing the connection. + */ + rc = wait_for_completion_interruptible(&conn->rx_login_comp); + if (rc < 0 || iscsi_target_check_conn_state(conn)) + return 0; + + if (!conn->conn_transport->iscsit_get_rx_pdu) + return 0; + + conn->conn_transport->iscsit_get_rx_pdu(conn); -transport_err: if (!signal_pending(current)) atomic_set(&conn->transport_failed, 1); iscsit_take_action_for_connection_exit(conn); From 42bc3e57877f2357ccbbc3ad6e6c7d9be44579f9 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:12 +0530 Subject: [PATCH 090/393] iscsi-target: add int (*iscsit_validate_params)() Add int (*iscsit_validate_params)() to struct iscsit_transport, iscsi-target uses this callback for validating conn operational parameters. cxgbit.ko needs this callback to check and update the value of MAXXMITDATASEGMENTLENGTH. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target_login.c | 10 ++++++++++ include/target/iscsi/iscsi_transport.h | 1 + 2 files changed, 11 insertions(+) diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c index 8436d56c5f0c..83574eea0be7 100644 --- a/drivers/target/iscsi/iscsi_target_login.c +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -1387,6 +1387,16 @@ static int __iscsi_target_login_thread(struct iscsi_np *np) goto old_sess_out; } + if (conn->conn_transport->iscsit_validate_params) { + ret = conn->conn_transport->iscsit_validate_params(conn); + if (ret < 0) { + if (zero_tsih) + goto new_sess_out; + else + goto old_sess_out; + } + } + ret = iscsi_target_start_negotiation(login, conn); if (ret < 0) goto new_sess_out; diff --git a/include/target/iscsi/iscsi_transport.h b/include/target/iscsi/iscsi_transport.h index ccc68a0334c2..46aa219be1ae 100644 --- a/include/target/iscsi/iscsi_transport.h +++ b/include/target/iscsi/iscsi_transport.h @@ -26,6 +26,7 @@ struct iscsit_transport { struct iscsi_datain_req *, const void *, u32); void (*iscsit_release_cmd)(struct iscsi_conn *, struct iscsi_cmd *); void (*iscsit_get_rx_pdu)(struct iscsi_conn *); + int (*iscsit_validate_params)(struct iscsi_conn *); enum target_prot_op (*iscsit_get_sup_prot_ops)(struct iscsi_conn *); }; From 8567270d8de6192eb2632b45421b1fde9bcda2f2 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:13 +0530 Subject: [PATCH 091/393] iscsi-target: add void (*iscsit_get_r2t_ttt)() Add void (*iscsit_get_r2t_ttt)() to struct iscsit_transport, iscsi-target uses this callback to get r2t->targ_xfer_tag. cxgbit.ko needs this callback for Direct Data Placement of Data Out pdus, adapter uses ttt in Data Out pdus for placing data directly in to the host buffers. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 5 ++++- include/target/iscsi/iscsi_transport.h | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index a827b7afe42c..a28eb87d6efc 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -3039,7 +3039,10 @@ static int iscsit_send_r2t( int_to_scsilun(cmd->se_cmd.orig_fe_lun, (struct scsi_lun *)&hdr->lun); hdr->itt = cmd->init_task_tag; - r2t->targ_xfer_tag = session_get_next_ttt(conn->sess); + if (conn->conn_transport->iscsit_get_r2t_ttt) + conn->conn_transport->iscsit_get_r2t_ttt(conn, cmd, r2t); + else + r2t->targ_xfer_tag = session_get_next_ttt(conn->sess); hdr->ttt = cpu_to_be32(r2t->targ_xfer_tag); hdr->statsn = cpu_to_be32(conn->stat_sn); hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); diff --git a/include/target/iscsi/iscsi_transport.h b/include/target/iscsi/iscsi_transport.h index 46aa219be1ae..32704b214be5 100644 --- a/include/target/iscsi/iscsi_transport.h +++ b/include/target/iscsi/iscsi_transport.h @@ -27,6 +27,8 @@ struct iscsit_transport { void (*iscsit_release_cmd)(struct iscsi_conn *, struct iscsi_cmd *); void (*iscsit_get_rx_pdu)(struct iscsi_conn *); int (*iscsit_validate_params)(struct iscsi_conn *); + void (*iscsit_get_r2t_ttt)(struct iscsi_conn *, struct iscsi_cmd *, + struct iscsi_r2t *); enum target_prot_op (*iscsit_get_sup_prot_ops)(struct iscsi_conn *); }; From c0b7373be811307c5ff1de8c8547aae3597d24aa Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:14 +0530 Subject: [PATCH 092/393] iscsi-target: move iscsit_thread_check_cpumask() Move iscsit_thread_check_cpumask() to header file so that ISCSI_HW_OFFLOAD and other transport drivers can call this function to ensure both tx and rx thread runs on same cpu. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 26 ------------------------ include/target/iscsi/iscsi_target_core.h | 26 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index a28eb87d6efc..28fc7fb5035f 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -3580,32 +3580,6 @@ void iscsit_thread_get_cpumask(struct iscsi_conn *conn) cpumask_setall(conn->conn_cpumask); } -static inline void iscsit_thread_check_cpumask( - struct iscsi_conn *conn, - struct task_struct *p, - int mode) -{ - /* - * mode == 1 signals iscsi_target_tx_thread() usage. - * mode == 0 signals iscsi_target_rx_thread() usage. - */ - if (mode == 1) { - if (!conn->conn_tx_reset_cpumask) - return; - conn->conn_tx_reset_cpumask = 0; - } else { - if (!conn->conn_rx_reset_cpumask) - return; - conn->conn_rx_reset_cpumask = 0; - } - /* - * Update the CPU mask for this single kthread so that - * both TX and RX kthreads are scheduled to run on the - * same CPU. - */ - set_cpus_allowed_ptr(p, conn->conn_cpumask); -} - static int iscsit_immediate_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) { diff --git a/include/target/iscsi/iscsi_target_core.h b/include/target/iscsi/iscsi_target_core.h index c3371fa548cb..01c41f2b66d1 100644 --- a/include/target/iscsi/iscsi_target_core.h +++ b/include/target/iscsi/iscsi_target_core.h @@ -890,4 +890,30 @@ static inline u32 session_get_next_ttt(struct iscsi_session *session) } extern struct iscsi_cmd *iscsit_find_cmd_from_itt(struct iscsi_conn *, itt_t); + +static inline void iscsit_thread_check_cpumask( + struct iscsi_conn *conn, + struct task_struct *p, + int mode) +{ + /* + * mode == 1 signals iscsi_target_tx_thread() usage. + * mode == 0 signals iscsi_target_rx_thread() usage. + */ + if (mode == 1) { + if (!conn->conn_tx_reset_cpumask) + return; + conn->conn_tx_reset_cpumask = 0; + } else { + if (!conn->conn_rx_reset_cpumask) + return; + conn->conn_rx_reset_cpumask = 0; + } + /* + * Update the CPU mask for this single kthread so that + * both TX and RX kthreads are scheduled to run on the + * same CPU. + */ + set_cpus_allowed_ptr(p, conn->conn_cpumask); +} #endif /* ISCSI_TARGET_CORE_H */ From 864e504ae2bbdd7efcd6923075047a7a6b5ade02 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:15 +0530 Subject: [PATCH 093/393] iscsi-target: use conn_transport->transport_type in text rsp Use conn_transport->transport_type instead of ISCSI_TCP to build text response. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 28fc7fb5035f..348337c539f0 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -3513,7 +3513,8 @@ static int iscsit_send_text_rsp( struct iscsi_text_rsp *hdr = (struct iscsi_text_rsp *)cmd->pdu; int text_length; - text_length = iscsit_build_text_rsp(cmd, conn, hdr, ISCSI_TCP); + text_length = iscsit_build_text_rsp(cmd, conn, hdr, + conn->conn_transport->transport_type); if (text_length < 0) return text_length; From 1c46960e3310adf6ac95078210fdb16987c3b31d Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:16 +0530 Subject: [PATCH 094/393] iscsi-target: add new offload transport type Add new transport type ISCSI_HW_OFFLOAD, hw offload transport drivers will use this transport type. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target_configfs.c | 76 ++++++++++++++++++++ include/target/iscsi/iscsi_target_core.h | 1 + 2 files changed, 77 insertions(+) diff --git a/drivers/target/iscsi/iscsi_target_configfs.c b/drivers/target/iscsi/iscsi_target_configfs.c index 97e5b69e0668..3565e07ad8a6 100644 --- a/drivers/target/iscsi/iscsi_target_configfs.c +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -182,12 +182,88 @@ static ssize_t lio_target_np_iser_store(struct config_item *item, return rc; } +static ssize_t lio_target_np_hw_offload_show(struct config_item *item, + char *page) +{ + struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); + struct iscsi_tpg_np *tpg_np_hw_offload; + ssize_t rb; + + tpg_np_hw_offload = iscsit_tpg_locate_child_np(tpg_np, + ISCSI_HW_OFFLOAD); + if (tpg_np_hw_offload) + rb = sprintf(page, "1\n"); + else + rb = sprintf(page, "0\n"); + + return rb; +} + +static ssize_t lio_target_np_hw_offload_store(struct config_item *item, + const char *page, size_t count) +{ + struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); + struct iscsi_np *np; + struct iscsi_portal_group *tpg; + struct iscsi_tpg_np *tpg_np_hw_offload = NULL; + u32 op; + int rc = 0; + + rc = kstrtou32(page, 0, &op); + if (rc) + return rc; + + if ((op != 1) && (op != 0)) { + pr_err("Illegal value for tpg_enable: %u\n", op); + return -EINVAL; + } + + np = tpg_np->tpg_np; + if (!np) { + pr_err("Unable to locate struct iscsi_np from" + " struct iscsi_tpg_np\n"); + return -EINVAL; + } + + tpg = tpg_np->tpg; + if (iscsit_get_tpg(tpg) < 0) + return -EINVAL; + + if (op) { + tpg_np_hw_offload = iscsit_tpg_add_network_portal(tpg, + &np->np_sockaddr, tpg_np, ISCSI_HW_OFFLOAD); + + if (IS_ERR(tpg_np_hw_offload)) { + rc = PTR_ERR(tpg_np_hw_offload); + goto out; + } + } else { + tpg_np_hw_offload = iscsit_tpg_locate_child_np(tpg_np, + ISCSI_HW_OFFLOAD); + + if (tpg_np_hw_offload) { + rc = iscsit_tpg_del_network_portal(tpg, + tpg_np_hw_offload); + if (rc < 0) + goto out; + } + } + + iscsit_put_tpg(tpg); + return count; +out: + iscsit_put_tpg(tpg); + return rc; +} + CONFIGFS_ATTR(lio_target_np_, sctp); CONFIGFS_ATTR(lio_target_np_, iser); +CONFIGFS_ATTR(lio_target_np_, hw_offload); static struct configfs_attribute *lio_target_portal_attrs[] = { &lio_target_np_attr_sctp, &lio_target_np_attr_iser, + &lio_target_np_attr_hw_offload, NULL, }; diff --git a/include/target/iscsi/iscsi_target_core.h b/include/target/iscsi/iscsi_target_core.h index 01c41f2b66d1..110c1fde7c68 100644 --- a/include/target/iscsi/iscsi_target_core.h +++ b/include/target/iscsi/iscsi_target_core.h @@ -74,6 +74,7 @@ enum iscsit_transport_type { ISCSI_IWARP_TCP = 3, ISCSI_IWARP_SCTP = 4, ISCSI_INFINIBAND = 5, + ISCSI_HW_OFFLOAD = 6, }; /* RFC-3720 7.1.4 Standard Connection State Diagram for a Target */ From 1c50ff424e3d9a392ec2df797c3b6083ed70fca6 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:17 +0530 Subject: [PATCH 095/393] iscsi-target: clear tx_thread_active clear tx_thread_active for ISCSI_HW_OFFLOAD transport in logout_post_handler functions. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 348337c539f0..29df956ed0cc 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -4379,7 +4379,8 @@ static void iscsit_logout_post_handler_closesession( * always sleep waiting for RX/TX thread shutdown to complete * within iscsit_close_connection(). */ - if (conn->conn_transport->transport_type == ISCSI_TCP) + if ((conn->conn_transport->transport_type == ISCSI_TCP) || + (conn->conn_transport->transport_type == ISCSI_HW_OFFLOAD)) sleep = cmpxchg(&conn->tx_thread_active, true, false); atomic_set(&conn->conn_logout_remove, 0); @@ -4396,7 +4397,8 @@ static void iscsit_logout_post_handler_samecid( { int sleep = 1; - if (conn->conn_transport->transport_type == ISCSI_TCP) + if ((conn->conn_transport->transport_type == ISCSI_TCP) || + (conn->conn_transport->transport_type == ISCSI_HW_OFFLOAD)) sleep = cmpxchg(&conn->tx_thread_active, true, false); atomic_set(&conn->conn_logout_remove, 0); From b4869ee9525dd6ded57f1898b65035d5529acfa0 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:18 +0530 Subject: [PATCH 096/393] iscsi-target: call complete on conn_logout_comp ISCSI_HW_OFFLOAD transport drivers waits on conn_logout_comp as ISCSI_TCP driver so call complete if transport type is ISCSI_HW_OFFLOAD. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 29df956ed0cc..8021223ca288 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -4063,16 +4063,18 @@ int iscsit_close_connection( pr_debug("Closing iSCSI connection CID %hu on SID:" " %u\n", conn->cid, sess->sid); /* - * Always up conn_logout_comp for the traditional TCP case just in case - * the RX Thread in iscsi_target_rx_opcode() is sleeping and the logout - * response never got sent because the connection failed. + * Always up conn_logout_comp for the traditional TCP and HW_OFFLOAD + * case just in case the RX Thread in iscsi_target_rx_opcode() is + * sleeping and the logout response never got sent because the + * connection failed. * * However for iser-target, isert_wait4logout() is using conn_logout_comp * to signal logout response TX interrupt completion. Go ahead and skip * this for iser since isert_rx_opcode() does not wait on logout failure, * and to avoid iscsi_conn pointer dereference in iser-target code. */ - if (conn->conn_transport->transport_type == ISCSI_TCP) + if ((conn->conn_transport->transport_type == ISCSI_TCP) || + (conn->conn_transport->transport_type == ISCSI_HW_OFFLOAD)) complete(&conn->conn_logout_comp); if (!strcmp(current->comm, ISCSI_RX_THREAD_NAME)) { From d2faaefb8d4c63fbc680512b04f9eb57667e2682 Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:19 +0530 Subject: [PATCH 097/393] iscsi-target: export symbols export symbols for ISCSI_HW_OFFLOAD transport drivers. Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 25 +++++++++----- .../target/iscsi/iscsi_target_datain_values.c | 1 + drivers/target/iscsi/iscsi_target_login.c | 3 +- drivers/target/iscsi/iscsi_target_nego.c | 1 + .../target/iscsi/iscsi_target_parameters.c | 1 + drivers/target/iscsi/iscsi_target_util.c | 2 ++ include/target/iscsi/iscsi_transport.h | 33 +++++++++++++++++++ 7 files changed, 56 insertions(+), 10 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 8021223ca288..487d8f37b23a 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -478,17 +478,16 @@ int iscsit_del_np(struct iscsi_np *np) return 0; } -static int iscsit_immediate_queue(struct iscsi_conn *, struct iscsi_cmd *, int); -static int iscsit_response_queue(struct iscsi_conn *, struct iscsi_cmd *, int); static void iscsit_get_rx_pdu(struct iscsi_conn *); -static int iscsit_queue_rsp(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +int iscsit_queue_rsp(struct iscsi_conn *conn, struct iscsi_cmd *cmd) { iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state); return 0; } +EXPORT_SYMBOL(iscsit_queue_rsp); -static void iscsit_aborted_task(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +void iscsit_aborted_task(struct iscsi_conn *conn, struct iscsi_cmd *cmd) { bool scsi_cmd = (cmd->iscsi_opcode == ISCSI_OP_SCSI_CMD); @@ -499,6 +498,7 @@ static void iscsit_aborted_task(struct iscsi_conn *conn, struct iscsi_cmd *cmd) __iscsit_free_cmd(cmd, scsi_cmd, true); } +EXPORT_SYMBOL(iscsit_aborted_task); static void iscsit_do_crypto_hash_buf(struct ahash_request *, const void *, u32, u32, u8 *, u8 *); @@ -799,7 +799,7 @@ static void __exit iscsi_target_cleanup_module(void) kfree(iscsit_global); } -static int iscsit_add_reject( +int iscsit_add_reject( struct iscsi_conn *conn, u8 reason, unsigned char *buf) @@ -829,6 +829,7 @@ static int iscsit_add_reject( return -1; } +EXPORT_SYMBOL(iscsit_add_reject); static int iscsit_add_reject_from_cmd( struct iscsi_cmd *cmd, @@ -884,6 +885,7 @@ int iscsit_reject_cmd(struct iscsi_cmd *cmd, u8 reason, unsigned char *buf) { return iscsit_add_reject_from_cmd(cmd, reason, false, buf); } +EXPORT_SYMBOL(iscsit_reject_cmd); /* * Map some portion of the allocated scatterlist to an iovec, suitable for @@ -2500,7 +2502,7 @@ iscsit_handle_logout_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, } EXPORT_SYMBOL(iscsit_handle_logout_cmd); -static int iscsit_handle_snack( +int iscsit_handle_snack( struct iscsi_conn *conn, unsigned char *buf) { @@ -2553,6 +2555,7 @@ static int iscsit_handle_snack( return 0; } +EXPORT_SYMBOL(iscsit_handle_snack); static void iscsit_rx_thread_wait_for_tcp(struct iscsi_conn *conn) { @@ -2733,7 +2736,7 @@ static void iscsit_tx_thread_wait_for_tcp(struct iscsi_conn *conn) } } -static void +void iscsit_build_datain_pdu(struct iscsi_cmd *cmd, struct iscsi_conn *conn, struct iscsi_datain *datain, struct iscsi_data_rsp *hdr, bool set_statsn) @@ -2777,6 +2780,7 @@ iscsit_build_datain_pdu(struct iscsi_cmd *cmd, struct iscsi_conn *conn, cmd->init_task_tag, ntohl(hdr->statsn), ntohl(hdr->datasn), ntohl(hdr->offset), datain->length, conn->cid); } +EXPORT_SYMBOL(iscsit_build_datain_pdu); static int iscsit_send_datain(struct iscsi_cmd *cmd, struct iscsi_conn *conn) { @@ -3153,6 +3157,7 @@ int iscsit_build_r2ts_for_cmd( return 0; } +EXPORT_SYMBOL(iscsit_build_r2ts_for_cmd); void iscsit_build_rsp_pdu(struct iscsi_cmd *cmd, struct iscsi_conn *conn, bool inc_stat_sn, struct iscsi_scsi_rsp *hdr) @@ -3581,7 +3586,7 @@ void iscsit_thread_get_cpumask(struct iscsi_conn *conn) cpumask_setall(conn->conn_cpumask); } -static int +int iscsit_immediate_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) { int ret; @@ -3623,6 +3628,7 @@ iscsit_immediate_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state err: return -1; } +EXPORT_SYMBOL(iscsit_immediate_queue); static int iscsit_handle_immediate_queue(struct iscsi_conn *conn) @@ -3647,7 +3653,7 @@ iscsit_handle_immediate_queue(struct iscsi_conn *conn) return 0; } -static int +int iscsit_response_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) { int ret; @@ -3752,6 +3758,7 @@ iscsit_response_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) err: return -1; } +EXPORT_SYMBOL(iscsit_response_queue); static int iscsit_handle_response_queue(struct iscsi_conn *conn) { diff --git a/drivers/target/iscsi/iscsi_target_datain_values.c b/drivers/target/iscsi/iscsi_target_datain_values.c index fb3b52b124ac..647d4a5dca52 100644 --- a/drivers/target/iscsi/iscsi_target_datain_values.c +++ b/drivers/target/iscsi/iscsi_target_datain_values.c @@ -524,3 +524,4 @@ struct iscsi_datain_req *iscsit_get_datain_values( return NULL; } +EXPORT_SYMBOL(iscsit_get_datain_values); diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c index 83574eea0be7..a09498f16690 100644 --- a/drivers/target/iscsi/iscsi_target_login.c +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -258,7 +258,7 @@ static void iscsi_login_set_conn_values( mutex_unlock(&auth_id_lock); } -static __printf(2, 3) int iscsi_change_param_sprintf( +__printf(2, 3) int iscsi_change_param_sprintf( struct iscsi_conn *conn, const char *fmt, ...) { @@ -279,6 +279,7 @@ static __printf(2, 3) int iscsi_change_param_sprintf( return 0; } +EXPORT_SYMBOL(iscsi_change_param_sprintf); /* * This is the leading connection of a new session, diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c index 9fc9117d0f22..ca061320f5eb 100644 --- a/drivers/target/iscsi/iscsi_target_nego.c +++ b/drivers/target/iscsi/iscsi_target_nego.c @@ -269,6 +269,7 @@ int iscsi_target_check_login_request( return 0; } +EXPORT_SYMBOL(iscsi_target_check_login_request); static int iscsi_target_check_first_request( struct iscsi_conn *conn, diff --git a/drivers/target/iscsi/iscsi_target_parameters.c b/drivers/target/iscsi/iscsi_target_parameters.c index 3a1f9a7e6bb6..0efa80bb8962 100644 --- a/drivers/target/iscsi/iscsi_target_parameters.c +++ b/drivers/target/iscsi/iscsi_target_parameters.c @@ -680,6 +680,7 @@ struct iscsi_param *iscsi_find_param_from_key( pr_err("Unable to locate key \"%s\".\n", key); return NULL; } +EXPORT_SYMBOL(iscsi_find_param_from_key); int iscsi_extract_key_value(char *textbuf, char **key, char **value) { diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c index a5330171ebe5..43e176f1048e 100644 --- a/drivers/target/iscsi/iscsi_target_util.c +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -514,6 +514,7 @@ void iscsit_add_cmd_to_immediate_queue( wake_up(&conn->queues_wq); } +EXPORT_SYMBOL(iscsit_add_cmd_to_immediate_queue); struct iscsi_queue_req *iscsit_get_cmd_from_immediate_queue(struct iscsi_conn *conn) { @@ -776,6 +777,7 @@ void iscsit_free_cmd(struct iscsi_cmd *cmd, bool shutdown) break; } } +EXPORT_SYMBOL(iscsit_free_cmd); int iscsit_check_session_usage_count(struct iscsi_session *sess) { diff --git a/include/target/iscsi/iscsi_transport.h b/include/target/iscsi/iscsi_transport.h index 32704b214be5..3c2e10aac49c 100644 --- a/include/target/iscsi/iscsi_transport.h +++ b/include/target/iscsi/iscsi_transport.h @@ -84,6 +84,18 @@ extern void iscsit_build_reject(struct iscsi_cmd *, struct iscsi_conn *, extern int iscsit_build_logout_rsp(struct iscsi_cmd *, struct iscsi_conn *, struct iscsi_logout_rsp *); extern int iscsit_logout_post_handler(struct iscsi_cmd *, struct iscsi_conn *); +extern int iscsit_queue_rsp(struct iscsi_conn *, struct iscsi_cmd *); +extern void iscsit_aborted_task(struct iscsi_conn *, struct iscsi_cmd *); +extern int iscsit_add_reject(struct iscsi_conn *, u8, unsigned char *); +extern int iscsit_reject_cmd(struct iscsi_cmd *, u8, unsigned char *); +extern int iscsit_handle_snack(struct iscsi_conn *, unsigned char *); +extern void iscsit_build_datain_pdu(struct iscsi_cmd *, struct iscsi_conn *, + struct iscsi_datain *, + struct iscsi_data_rsp *, bool); +extern int iscsit_build_r2ts_for_cmd(struct iscsi_conn *, struct iscsi_cmd *, + bool); +extern int iscsit_immediate_queue(struct iscsi_conn *, struct iscsi_cmd *, int); +extern int iscsit_response_queue(struct iscsi_conn *, struct iscsi_cmd *, int); /* * From iscsi_target_device.c */ @@ -109,3 +121,24 @@ extern struct iscsi_cmd *iscsit_allocate_cmd(struct iscsi_conn *, int); extern int iscsit_sequence_cmd(struct iscsi_conn *, struct iscsi_cmd *, unsigned char *, __be32); extern void iscsit_release_cmd(struct iscsi_cmd *); +extern void iscsit_free_cmd(struct iscsi_cmd *, bool); +extern void iscsit_add_cmd_to_immediate_queue(struct iscsi_cmd *, + struct iscsi_conn *, u8); + +/* + * From iscsi_target_nego.c + */ +extern int iscsi_target_check_login_request(struct iscsi_conn *, + struct iscsi_login *); + +/* + * From iscsi_target_login.c + */ +extern __printf(2, 3) int iscsi_change_param_sprintf( + struct iscsi_conn *, const char *, ...); + +/* + * From iscsi_target_parameters.c + */ +extern struct iscsi_param *iscsi_find_param_from_key( + char *, struct iscsi_param_list *); From 9730ffcb8957e1ce9e7d903f7a5db09038a9db8d Mon Sep 17 00:00:00 2001 From: Varun Prakash Date: Wed, 20 Apr 2016 00:00:20 +0530 Subject: [PATCH 098/393] cxgbit: add files for cxgbit.ko cxgbit.h - This file contains data structure definitions for cxgbit.ko. cxgbit_lro.h - This file contains data structure definitions for LRO support. cxgbit_main.c - This file contains code for registering with iscsi target transport and cxgb4 driver. cxgbit_cm.c - This file contains code for connection management. cxgbit_target.c - This file contains code for processing iSCSI PDU. cxgbit_ddp.c - This file contains code for Direct Data Placement. (added check for NULL sg in cxgbit_set_one_ppod) Reported-by: Dan Carpenter (add Kconfig and Makefile v2: added dependency on INET) Reported-by: Arnd Bergmann Signed-off-by: Varun Prakash Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/Kconfig | 2 + drivers/target/iscsi/Makefile | 1 + drivers/target/iscsi/cxgbit/Kconfig | 7 + drivers/target/iscsi/cxgbit/Makefile | 6 + drivers/target/iscsi/cxgbit/cxgbit.h | 353 ++++ drivers/target/iscsi/cxgbit/cxgbit_cm.c | 2086 +++++++++++++++++++ drivers/target/iscsi/cxgbit/cxgbit_ddp.c | 325 +++ drivers/target/iscsi/cxgbit/cxgbit_lro.h | 72 + drivers/target/iscsi/cxgbit/cxgbit_main.c | 701 +++++++ drivers/target/iscsi/cxgbit/cxgbit_target.c | 1561 ++++++++++++++ 10 files changed, 5114 insertions(+) create mode 100644 drivers/target/iscsi/cxgbit/Kconfig create mode 100644 drivers/target/iscsi/cxgbit/Makefile create mode 100644 drivers/target/iscsi/cxgbit/cxgbit.h create mode 100644 drivers/target/iscsi/cxgbit/cxgbit_cm.c create mode 100644 drivers/target/iscsi/cxgbit/cxgbit_ddp.c create mode 100644 drivers/target/iscsi/cxgbit/cxgbit_lro.h create mode 100644 drivers/target/iscsi/cxgbit/cxgbit_main.c create mode 100644 drivers/target/iscsi/cxgbit/cxgbit_target.c diff --git a/drivers/target/iscsi/Kconfig b/drivers/target/iscsi/Kconfig index 8345fb457a40..bbdbf9c4e93a 100644 --- a/drivers/target/iscsi/Kconfig +++ b/drivers/target/iscsi/Kconfig @@ -7,3 +7,5 @@ config ISCSI_TARGET help Say M here to enable the ConfigFS enabled Linux-iSCSI.org iSCSI Target Mode Stack. + +source "drivers/target/iscsi/cxgbit/Kconfig" diff --git a/drivers/target/iscsi/Makefile b/drivers/target/iscsi/Makefile index 0f43be9c3453..0f18295e05bc 100644 --- a/drivers/target/iscsi/Makefile +++ b/drivers/target/iscsi/Makefile @@ -18,3 +18,4 @@ iscsi_target_mod-y += iscsi_target_parameters.o \ iscsi_target_transport.o obj-$(CONFIG_ISCSI_TARGET) += iscsi_target_mod.o +obj-$(CONFIG_ISCSI_TARGET_CXGB4) += cxgbit/ diff --git a/drivers/target/iscsi/cxgbit/Kconfig b/drivers/target/iscsi/cxgbit/Kconfig new file mode 100644 index 000000000000..c9b6a3c758b1 --- /dev/null +++ b/drivers/target/iscsi/cxgbit/Kconfig @@ -0,0 +1,7 @@ +config ISCSI_TARGET_CXGB4 + tristate "Chelsio iSCSI target offload driver" + depends on ISCSI_TARGET && CHELSIO_T4 && INET + select CHELSIO_T4_UWIRE + ---help--- + To compile this driver as module, choose M here: the module + will be called cxgbit. diff --git a/drivers/target/iscsi/cxgbit/Makefile b/drivers/target/iscsi/cxgbit/Makefile new file mode 100644 index 000000000000..bd56c073dff6 --- /dev/null +++ b/drivers/target/iscsi/cxgbit/Makefile @@ -0,0 +1,6 @@ +ccflags-y := -Idrivers/net/ethernet/chelsio/cxgb4 +ccflags-y += -Idrivers/target/iscsi + +obj-$(CONFIG_ISCSI_TARGET_CXGB4) += cxgbit.o + +cxgbit-y := cxgbit_main.o cxgbit_cm.o cxgbit_target.o cxgbit_ddp.o diff --git a/drivers/target/iscsi/cxgbit/cxgbit.h b/drivers/target/iscsi/cxgbit/cxgbit.h new file mode 100644 index 000000000000..625c7f6de6b2 --- /dev/null +++ b/drivers/target/iscsi/cxgbit/cxgbit.h @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2016 Chelsio Communications, Inc. + * + * 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. + */ + +#ifndef __CXGBIT_H__ +#define __CXGBIT_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include "t4_regs.h" +#include "t4_msg.h" +#include "cxgb4.h" +#include "cxgb4_uld.h" +#include "l2t.h" +#include "cxgb4_ppm.h" +#include "cxgbit_lro.h" + +extern struct mutex cdev_list_lock; +extern struct list_head cdev_list_head; +struct cxgbit_np; + +struct cxgbit_sock; + +struct cxgbit_cmd { + struct scatterlist sg; + struct cxgbi_task_tag_info ttinfo; + bool setup_ddp; + bool release; +}; + +#define CXGBIT_MAX_ISO_PAYLOAD \ + min_t(u32, MAX_SKB_FRAGS * PAGE_SIZE, 65535) + +struct cxgbit_iso_info { + u8 flags; + u32 mpdu; + u32 len; + u32 burst_len; +}; + +enum cxgbit_skcb_flags { + SKCBF_TX_NEED_HDR = (1 << 0), /* packet needs a header */ + SKCBF_TX_FLAG_COMPL = (1 << 1), /* wr completion flag */ + SKCBF_TX_ISO = (1 << 2), /* iso cpl in tx skb */ + SKCBF_RX_LRO = (1 << 3), /* lro skb */ +}; + +struct cxgbit_skb_rx_cb { + u8 opcode; + void *pdu_cb; + void (*backlog_fn)(struct cxgbit_sock *, struct sk_buff *); +}; + +struct cxgbit_skb_tx_cb { + u8 submode; + u32 extra_len; +}; + +union cxgbit_skb_cb { + struct { + u8 flags; + union { + struct cxgbit_skb_tx_cb tx; + struct cxgbit_skb_rx_cb rx; + }; + }; + + struct { + /* This member must be first. */ + struct l2t_skb_cb l2t; + struct sk_buff *wr_next; + }; +}; + +#define CXGBIT_SKB_CB(skb) ((union cxgbit_skb_cb *)&((skb)->cb[0])) +#define cxgbit_skcb_flags(skb) (CXGBIT_SKB_CB(skb)->flags) +#define cxgbit_skcb_submode(skb) (CXGBIT_SKB_CB(skb)->tx.submode) +#define cxgbit_skcb_tx_wr_next(skb) (CXGBIT_SKB_CB(skb)->wr_next) +#define cxgbit_skcb_tx_extralen(skb) (CXGBIT_SKB_CB(skb)->tx.extra_len) +#define cxgbit_skcb_rx_opcode(skb) (CXGBIT_SKB_CB(skb)->rx.opcode) +#define cxgbit_skcb_rx_backlog_fn(skb) (CXGBIT_SKB_CB(skb)->rx.backlog_fn) +#define cxgbit_rx_pdu_cb(skb) (CXGBIT_SKB_CB(skb)->rx.pdu_cb) + +static inline void *cplhdr(struct sk_buff *skb) +{ + return skb->data; +} + +enum cxgbit_cdev_flags { + CDEV_STATE_UP = 0, + CDEV_ISO_ENABLE, + CDEV_DDP_ENABLE, +}; + +#define NP_INFO_HASH_SIZE 32 + +struct np_info { + struct np_info *next; + struct cxgbit_np *cnp; + unsigned int stid; +}; + +struct cxgbit_list_head { + struct list_head list; + /* device lock */ + spinlock_t lock; +}; + +struct cxgbit_device { + struct list_head list; + struct cxgb4_lld_info lldi; + struct np_info *np_hash_tab[NP_INFO_HASH_SIZE]; + /* np lock */ + spinlock_t np_lock; + u8 selectq[MAX_NPORTS][2]; + struct cxgbit_list_head cskq; + u32 mdsl; + struct kref kref; + unsigned long flags; +}; + +struct cxgbit_wr_wait { + struct completion completion; + int ret; +}; + +enum cxgbit_csk_state { + CSK_STATE_IDLE = 0, + CSK_STATE_LISTEN, + CSK_STATE_CONNECTING, + CSK_STATE_ESTABLISHED, + CSK_STATE_ABORTING, + CSK_STATE_CLOSING, + CSK_STATE_MORIBUND, + CSK_STATE_DEAD, +}; + +enum cxgbit_csk_flags { + CSK_TX_DATA_SENT = 0, + CSK_LOGIN_PDU_DONE, + CSK_LOGIN_DONE, + CSK_DDP_ENABLE, +}; + +struct cxgbit_sock_common { + struct cxgbit_device *cdev; + struct sockaddr_storage local_addr; + struct sockaddr_storage remote_addr; + struct cxgbit_wr_wait wr_wait; + enum cxgbit_csk_state state; + unsigned long flags; +}; + +struct cxgbit_np { + struct cxgbit_sock_common com; + wait_queue_head_t accept_wait; + struct iscsi_np *np; + struct completion accept_comp; + struct list_head np_accept_list; + /* np accept lock */ + spinlock_t np_accept_lock; + struct kref kref; + unsigned int stid; +}; + +struct cxgbit_sock { + struct cxgbit_sock_common com; + struct cxgbit_np *cnp; + struct iscsi_conn *conn; + struct l2t_entry *l2t; + struct dst_entry *dst; + struct list_head list; + struct sk_buff_head rxq; + struct sk_buff_head txq; + struct sk_buff_head ppodq; + struct sk_buff_head backlogq; + struct sk_buff_head skbq; + struct sk_buff *wr_pending_head; + struct sk_buff *wr_pending_tail; + struct sk_buff *skb; + struct sk_buff *lro_skb; + struct sk_buff *lro_hskb; + struct list_head accept_node; + /* socket lock */ + spinlock_t lock; + wait_queue_head_t waitq; + wait_queue_head_t ack_waitq; + bool lock_owner; + struct kref kref; + u32 max_iso_npdu; + u32 wr_cred; + u32 wr_una_cred; + u32 wr_max_cred; + u32 snd_una; + u32 tid; + u32 snd_nxt; + u32 rcv_nxt; + u32 smac_idx; + u32 tx_chan; + u32 mtu; + u32 write_seq; + u32 rx_credits; + u32 snd_win; + u32 rcv_win; + u16 mss; + u16 emss; + u16 plen; + u16 rss_qid; + u16 txq_idx; + u16 ctrlq_idx; + u8 tos; + u8 port_id; +#define CXGBIT_SUBMODE_HCRC 0x1 +#define CXGBIT_SUBMODE_DCRC 0x2 + u8 submode; +#ifdef CONFIG_CHELSIO_T4_DCB + u8 dcb_priority; +#endif + u8 snd_wscale; +}; + +void _cxgbit_free_cdev(struct kref *kref); +void _cxgbit_free_csk(struct kref *kref); +void _cxgbit_free_cnp(struct kref *kref); + +static inline void cxgbit_get_cdev(struct cxgbit_device *cdev) +{ + kref_get(&cdev->kref); +} + +static inline void cxgbit_put_cdev(struct cxgbit_device *cdev) +{ + kref_put(&cdev->kref, _cxgbit_free_cdev); +} + +static inline void cxgbit_get_csk(struct cxgbit_sock *csk) +{ + kref_get(&csk->kref); +} + +static inline void cxgbit_put_csk(struct cxgbit_sock *csk) +{ + kref_put(&csk->kref, _cxgbit_free_csk); +} + +static inline void cxgbit_get_cnp(struct cxgbit_np *cnp) +{ + kref_get(&cnp->kref); +} + +static inline void cxgbit_put_cnp(struct cxgbit_np *cnp) +{ + kref_put(&cnp->kref, _cxgbit_free_cnp); +} + +static inline void cxgbit_sock_reset_wr_list(struct cxgbit_sock *csk) +{ + csk->wr_pending_tail = NULL; + csk->wr_pending_head = NULL; +} + +static inline struct sk_buff *cxgbit_sock_peek_wr(const struct cxgbit_sock *csk) +{ + return csk->wr_pending_head; +} + +static inline void +cxgbit_sock_enqueue_wr(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + cxgbit_skcb_tx_wr_next(skb) = NULL; + + skb_get(skb); + + if (!csk->wr_pending_head) + csk->wr_pending_head = skb; + else + cxgbit_skcb_tx_wr_next(csk->wr_pending_tail) = skb; + csk->wr_pending_tail = skb; +} + +static inline struct sk_buff *cxgbit_sock_dequeue_wr(struct cxgbit_sock *csk) +{ + struct sk_buff *skb = csk->wr_pending_head; + + if (likely(skb)) { + csk->wr_pending_head = cxgbit_skcb_tx_wr_next(skb); + cxgbit_skcb_tx_wr_next(skb) = NULL; + } + return skb; +} + +typedef void (*cxgbit_cplhandler_func)(struct cxgbit_device *, + struct sk_buff *); + +int cxgbit_setup_np(struct iscsi_np *, struct sockaddr_storage *); +int cxgbit_setup_conn_digest(struct cxgbit_sock *); +int cxgbit_accept_np(struct iscsi_np *, struct iscsi_conn *); +void cxgbit_free_np(struct iscsi_np *); +void cxgbit_free_conn(struct iscsi_conn *); +extern cxgbit_cplhandler_func cxgbit_cplhandlers[NUM_CPL_CMDS]; +int cxgbit_get_login_rx(struct iscsi_conn *, struct iscsi_login *); +int cxgbit_rx_data_ack(struct cxgbit_sock *); +int cxgbit_l2t_send(struct cxgbit_device *, struct sk_buff *, + struct l2t_entry *); +void cxgbit_push_tx_frames(struct cxgbit_sock *); +int cxgbit_put_login_tx(struct iscsi_conn *, struct iscsi_login *, u32); +int cxgbit_xmit_pdu(struct iscsi_conn *, struct iscsi_cmd *, + struct iscsi_datain_req *, const void *, u32); +void cxgbit_get_r2t_ttt(struct iscsi_conn *, struct iscsi_cmd *, + struct iscsi_r2t *); +u32 cxgbit_send_tx_flowc_wr(struct cxgbit_sock *); +int cxgbit_ofld_send(struct cxgbit_device *, struct sk_buff *); +void cxgbit_get_rx_pdu(struct iscsi_conn *); +int cxgbit_validate_params(struct iscsi_conn *); +struct cxgbit_device *cxgbit_find_device(struct net_device *, u8 *); + +/* DDP */ +int cxgbit_ddp_init(struct cxgbit_device *); +int cxgbit_setup_conn_pgidx(struct cxgbit_sock *, u32); +int cxgbit_reserve_ttt(struct cxgbit_sock *, struct iscsi_cmd *); +void cxgbit_release_cmd(struct iscsi_conn *, struct iscsi_cmd *); + +static inline +struct cxgbi_ppm *cdev2ppm(struct cxgbit_device *cdev) +{ + return (struct cxgbi_ppm *)(*cdev->lldi.iscsi_ppm); +} +#endif /* __CXGBIT_H__ */ diff --git a/drivers/target/iscsi/cxgbit/cxgbit_cm.c b/drivers/target/iscsi/cxgbit/cxgbit_cm.c new file mode 100644 index 000000000000..0ae0b131abfc --- /dev/null +++ b/drivers/target/iscsi/cxgbit/cxgbit_cm.c @@ -0,0 +1,2086 @@ +/* + * Copyright (c) 2016 Chelsio Communications, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "cxgbit.h" +#include "clip_tbl.h" + +static void cxgbit_init_wr_wait(struct cxgbit_wr_wait *wr_waitp) +{ + wr_waitp->ret = 0; + reinit_completion(&wr_waitp->completion); +} + +static void +cxgbit_wake_up(struct cxgbit_wr_wait *wr_waitp, const char *func, u8 ret) +{ + if (ret == CPL_ERR_NONE) + wr_waitp->ret = 0; + else + wr_waitp->ret = -EIO; + + if (wr_waitp->ret) + pr_err("%s: err:%u", func, ret); + + complete(&wr_waitp->completion); +} + +static int +cxgbit_wait_for_reply(struct cxgbit_device *cdev, + struct cxgbit_wr_wait *wr_waitp, u32 tid, u32 timeout, + const char *func) +{ + int ret; + + if (!test_bit(CDEV_STATE_UP, &cdev->flags)) { + wr_waitp->ret = -EIO; + goto out; + } + + ret = wait_for_completion_timeout(&wr_waitp->completion, timeout * HZ); + if (!ret) { + pr_info("%s - Device %s not responding tid %u\n", + func, pci_name(cdev->lldi.pdev), tid); + wr_waitp->ret = -ETIMEDOUT; + } +out: + if (wr_waitp->ret) + pr_info("%s: FW reply %d tid %u\n", + pci_name(cdev->lldi.pdev), wr_waitp->ret, tid); + return wr_waitp->ret; +} + +/* Returns whether a CPL status conveys negative advice. + */ +static int cxgbit_is_neg_adv(unsigned int status) +{ + return status == CPL_ERR_RTX_NEG_ADVICE || + status == CPL_ERR_PERSIST_NEG_ADVICE || + status == CPL_ERR_KEEPALV_NEG_ADVICE; +} + +static int cxgbit_np_hashfn(const struct cxgbit_np *cnp) +{ + return ((unsigned long)cnp >> 10) & (NP_INFO_HASH_SIZE - 1); +} + +static struct np_info * +cxgbit_np_hash_add(struct cxgbit_device *cdev, struct cxgbit_np *cnp, + unsigned int stid) +{ + struct np_info *p = kzalloc(sizeof(*p), GFP_KERNEL); + + if (p) { + int bucket = cxgbit_np_hashfn(cnp); + + p->cnp = cnp; + p->stid = stid; + spin_lock(&cdev->np_lock); + p->next = cdev->np_hash_tab[bucket]; + cdev->np_hash_tab[bucket] = p; + spin_unlock(&cdev->np_lock); + } + + return p; +} + +static int +cxgbit_np_hash_find(struct cxgbit_device *cdev, struct cxgbit_np *cnp) +{ + int stid = -1, bucket = cxgbit_np_hashfn(cnp); + struct np_info *p; + + spin_lock(&cdev->np_lock); + for (p = cdev->np_hash_tab[bucket]; p; p = p->next) { + if (p->cnp == cnp) { + stid = p->stid; + break; + } + } + spin_unlock(&cdev->np_lock); + + return stid; +} + +static int cxgbit_np_hash_del(struct cxgbit_device *cdev, struct cxgbit_np *cnp) +{ + int stid = -1, bucket = cxgbit_np_hashfn(cnp); + struct np_info *p, **prev = &cdev->np_hash_tab[bucket]; + + spin_lock(&cdev->np_lock); + for (p = *prev; p; prev = &p->next, p = p->next) { + if (p->cnp == cnp) { + stid = p->stid; + *prev = p->next; + kfree(p); + break; + } + } + spin_unlock(&cdev->np_lock); + + return stid; +} + +void _cxgbit_free_cnp(struct kref *kref) +{ + struct cxgbit_np *cnp; + + cnp = container_of(kref, struct cxgbit_np, kref); + kfree(cnp); +} + +static int +cxgbit_create_server6(struct cxgbit_device *cdev, unsigned int stid, + struct cxgbit_np *cnp) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) + &cnp->com.local_addr; + int addr_type; + int ret; + + pr_debug("%s: dev = %s; stid = %u; sin6_port = %u\n", + __func__, cdev->lldi.ports[0]->name, stid, sin6->sin6_port); + + addr_type = ipv6_addr_type((const struct in6_addr *) + &sin6->sin6_addr); + if (addr_type != IPV6_ADDR_ANY) { + ret = cxgb4_clip_get(cdev->lldi.ports[0], + (const u32 *)&sin6->sin6_addr.s6_addr, 1); + if (ret) { + pr_err("Unable to find clip table entry. laddr %pI6. Error:%d.\n", + sin6->sin6_addr.s6_addr, ret); + return -ENOMEM; + } + } + + cxgbit_get_cnp(cnp); + cxgbit_init_wr_wait(&cnp->com.wr_wait); + + ret = cxgb4_create_server6(cdev->lldi.ports[0], + stid, &sin6->sin6_addr, + sin6->sin6_port, + cdev->lldi.rxq_ids[0]); + if (!ret) + ret = cxgbit_wait_for_reply(cdev, &cnp->com.wr_wait, + 0, 10, __func__); + else if (ret > 0) + ret = net_xmit_errno(ret); + else + cxgbit_put_cnp(cnp); + + if (ret) { + if (ret != -ETIMEDOUT) + cxgb4_clip_release(cdev->lldi.ports[0], + (const u32 *)&sin6->sin6_addr.s6_addr, 1); + + pr_err("create server6 err %d stid %d laddr %pI6 lport %d\n", + ret, stid, sin6->sin6_addr.s6_addr, + ntohs(sin6->sin6_port)); + } + + return ret; +} + +static int +cxgbit_create_server4(struct cxgbit_device *cdev, unsigned int stid, + struct cxgbit_np *cnp) +{ + struct sockaddr_in *sin = (struct sockaddr_in *) + &cnp->com.local_addr; + int ret; + + pr_debug("%s: dev = %s; stid = %u; sin_port = %u\n", + __func__, cdev->lldi.ports[0]->name, stid, sin->sin_port); + + cxgbit_get_cnp(cnp); + cxgbit_init_wr_wait(&cnp->com.wr_wait); + + ret = cxgb4_create_server(cdev->lldi.ports[0], + stid, sin->sin_addr.s_addr, + sin->sin_port, 0, + cdev->lldi.rxq_ids[0]); + if (!ret) + ret = cxgbit_wait_for_reply(cdev, + &cnp->com.wr_wait, + 0, 10, __func__); + else if (ret > 0) + ret = net_xmit_errno(ret); + else + cxgbit_put_cnp(cnp); + + if (ret) + pr_err("create server failed err %d stid %d laddr %pI4 lport %d\n", + ret, stid, &sin->sin_addr, ntohs(sin->sin_port)); + return ret; +} + +struct cxgbit_device *cxgbit_find_device(struct net_device *ndev, u8 *port_id) +{ + struct cxgbit_device *cdev; + u8 i; + + list_for_each_entry(cdev, &cdev_list_head, list) { + struct cxgb4_lld_info *lldi = &cdev->lldi; + + for (i = 0; i < lldi->nports; i++) { + if (lldi->ports[i] == ndev) { + if (port_id) + *port_id = i; + return cdev; + } + } + } + + return NULL; +} + +static struct net_device *cxgbit_get_real_dev(struct net_device *ndev) +{ + if (ndev->priv_flags & IFF_BONDING) { + pr_err("Bond devices are not supported. Interface:%s\n", + ndev->name); + return NULL; + } + + if (is_vlan_dev(ndev)) + return vlan_dev_real_dev(ndev); + + return ndev; +} + +static struct net_device *cxgbit_ipv4_netdev(__be32 saddr) +{ + struct net_device *ndev; + + ndev = __ip_dev_find(&init_net, saddr, false); + if (!ndev) + return NULL; + + return cxgbit_get_real_dev(ndev); +} + +static struct net_device *cxgbit_ipv6_netdev(struct in6_addr *addr6) +{ + struct net_device *ndev = NULL; + bool found = false; + + if (IS_ENABLED(CONFIG_IPV6)) { + for_each_netdev_rcu(&init_net, ndev) + if (ipv6_chk_addr(&init_net, addr6, ndev, 1)) { + found = true; + break; + } + } + if (!found) + return NULL; + return cxgbit_get_real_dev(ndev); +} + +static struct cxgbit_device *cxgbit_find_np_cdev(struct cxgbit_np *cnp) +{ + struct sockaddr_storage *sockaddr = &cnp->com.local_addr; + int ss_family = sockaddr->ss_family; + struct net_device *ndev = NULL; + struct cxgbit_device *cdev = NULL; + + rcu_read_lock(); + if (ss_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)sockaddr; + ndev = cxgbit_ipv4_netdev(sin->sin_addr.s_addr); + } else if (ss_family == AF_INET6) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)sockaddr; + ndev = cxgbit_ipv6_netdev(&sin6->sin6_addr); + } + if (!ndev) + goto out; + + cdev = cxgbit_find_device(ndev, NULL); +out: + rcu_read_unlock(); + return cdev; +} + +static bool cxgbit_inaddr_any(struct cxgbit_np *cnp) +{ + struct sockaddr_storage *sockaddr = &cnp->com.local_addr; + int ss_family = sockaddr->ss_family; + int addr_type; + + if (ss_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)sockaddr; + if (sin->sin_addr.s_addr == htonl(INADDR_ANY)) + return true; + } else if (ss_family == AF_INET6) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)sockaddr; + addr_type = ipv6_addr_type((const struct in6_addr *) + &sin6->sin6_addr); + if (addr_type == IPV6_ADDR_ANY) + return true; + } + return false; +} + +static int +__cxgbit_setup_cdev_np(struct cxgbit_device *cdev, struct cxgbit_np *cnp) +{ + int stid, ret; + int ss_family = cnp->com.local_addr.ss_family; + + if (!test_bit(CDEV_STATE_UP, &cdev->flags)) + return -EINVAL; + + stid = cxgb4_alloc_stid(cdev->lldi.tids, ss_family, cnp); + if (stid < 0) + return -EINVAL; + + if (!cxgbit_np_hash_add(cdev, cnp, stid)) { + cxgb4_free_stid(cdev->lldi.tids, stid, ss_family); + return -EINVAL; + } + + if (ss_family == AF_INET) + ret = cxgbit_create_server4(cdev, stid, cnp); + else + ret = cxgbit_create_server6(cdev, stid, cnp); + + if (ret) { + if (ret != -ETIMEDOUT) + cxgb4_free_stid(cdev->lldi.tids, stid, + ss_family); + cxgbit_np_hash_del(cdev, cnp); + return ret; + } + return ret; +} + +static int cxgbit_setup_cdev_np(struct cxgbit_np *cnp) +{ + struct cxgbit_device *cdev; + int ret = -1; + + mutex_lock(&cdev_list_lock); + cdev = cxgbit_find_np_cdev(cnp); + if (!cdev) + goto out; + + if (cxgbit_np_hash_find(cdev, cnp) >= 0) + goto out; + + if (__cxgbit_setup_cdev_np(cdev, cnp)) + goto out; + + cnp->com.cdev = cdev; + ret = 0; +out: + mutex_unlock(&cdev_list_lock); + return ret; +} + +static int cxgbit_setup_all_np(struct cxgbit_np *cnp) +{ + struct cxgbit_device *cdev; + int ret; + u32 count = 0; + + mutex_lock(&cdev_list_lock); + list_for_each_entry(cdev, &cdev_list_head, list) { + if (cxgbit_np_hash_find(cdev, cnp) >= 0) { + mutex_unlock(&cdev_list_lock); + return -1; + } + } + + list_for_each_entry(cdev, &cdev_list_head, list) { + ret = __cxgbit_setup_cdev_np(cdev, cnp); + if (ret == -ETIMEDOUT) + break; + if (ret != 0) + continue; + count++; + } + mutex_unlock(&cdev_list_lock); + + return count ? 0 : -1; +} + +int cxgbit_setup_np(struct iscsi_np *np, struct sockaddr_storage *ksockaddr) +{ + struct cxgbit_np *cnp; + int ret; + + if ((ksockaddr->ss_family != AF_INET) && + (ksockaddr->ss_family != AF_INET6)) + return -EINVAL; + + cnp = kzalloc(sizeof(*cnp), GFP_KERNEL); + if (!cnp) + return -ENOMEM; + + init_waitqueue_head(&cnp->accept_wait); + init_completion(&cnp->com.wr_wait.completion); + init_completion(&cnp->accept_comp); + INIT_LIST_HEAD(&cnp->np_accept_list); + spin_lock_init(&cnp->np_accept_lock); + kref_init(&cnp->kref); + memcpy(&np->np_sockaddr, ksockaddr, + sizeof(struct sockaddr_storage)); + memcpy(&cnp->com.local_addr, &np->np_sockaddr, + sizeof(cnp->com.local_addr)); + + cnp->np = np; + cnp->com.cdev = NULL; + + if (cxgbit_inaddr_any(cnp)) + ret = cxgbit_setup_all_np(cnp); + else + ret = cxgbit_setup_cdev_np(cnp); + + if (ret) { + cxgbit_put_cnp(cnp); + return -EINVAL; + } + + np->np_context = cnp; + cnp->com.state = CSK_STATE_LISTEN; + return 0; +} + +static void +cxgbit_set_conn_info(struct iscsi_np *np, struct iscsi_conn *conn, + struct cxgbit_sock *csk) +{ + conn->login_family = np->np_sockaddr.ss_family; + conn->login_sockaddr = csk->com.remote_addr; + conn->local_sockaddr = csk->com.local_addr; +} + +int cxgbit_accept_np(struct iscsi_np *np, struct iscsi_conn *conn) +{ + struct cxgbit_np *cnp = np->np_context; + struct cxgbit_sock *csk; + int ret = 0; + +accept_wait: + ret = wait_for_completion_interruptible(&cnp->accept_comp); + if (ret) + return -ENODEV; + + spin_lock_bh(&np->np_thread_lock); + if (np->np_thread_state >= ISCSI_NP_THREAD_RESET) { + spin_unlock_bh(&np->np_thread_lock); + /** + * No point in stalling here when np_thread + * is in state RESET/SHUTDOWN/EXIT - bail + **/ + return -ENODEV; + } + spin_unlock_bh(&np->np_thread_lock); + + spin_lock_bh(&cnp->np_accept_lock); + if (list_empty(&cnp->np_accept_list)) { + spin_unlock_bh(&cnp->np_accept_lock); + goto accept_wait; + } + + csk = list_first_entry(&cnp->np_accept_list, + struct cxgbit_sock, + accept_node); + + list_del_init(&csk->accept_node); + spin_unlock_bh(&cnp->np_accept_lock); + conn->context = csk; + csk->conn = conn; + + cxgbit_set_conn_info(np, conn, csk); + return 0; +} + +static int +__cxgbit_free_cdev_np(struct cxgbit_device *cdev, struct cxgbit_np *cnp) +{ + int stid, ret; + bool ipv6 = false; + + stid = cxgbit_np_hash_del(cdev, cnp); + if (stid < 0) + return -EINVAL; + if (!test_bit(CDEV_STATE_UP, &cdev->flags)) + return -EINVAL; + + if (cnp->np->np_sockaddr.ss_family == AF_INET6) + ipv6 = true; + + cxgbit_get_cnp(cnp); + cxgbit_init_wr_wait(&cnp->com.wr_wait); + ret = cxgb4_remove_server(cdev->lldi.ports[0], stid, + cdev->lldi.rxq_ids[0], ipv6); + + if (ret > 0) + ret = net_xmit_errno(ret); + + if (ret) { + cxgbit_put_cnp(cnp); + return ret; + } + + ret = cxgbit_wait_for_reply(cdev, &cnp->com.wr_wait, + 0, 10, __func__); + if (ret == -ETIMEDOUT) + return ret; + + if (ipv6 && cnp->com.cdev) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)&cnp->com.local_addr; + cxgb4_clip_release(cdev->lldi.ports[0], + (const u32 *)&sin6->sin6_addr.s6_addr, + 1); + } + + cxgb4_free_stid(cdev->lldi.tids, stid, + cnp->com.local_addr.ss_family); + return 0; +} + +static void cxgbit_free_all_np(struct cxgbit_np *cnp) +{ + struct cxgbit_device *cdev; + int ret; + + mutex_lock(&cdev_list_lock); + list_for_each_entry(cdev, &cdev_list_head, list) { + ret = __cxgbit_free_cdev_np(cdev, cnp); + if (ret == -ETIMEDOUT) + break; + } + mutex_unlock(&cdev_list_lock); +} + +static void cxgbit_free_cdev_np(struct cxgbit_np *cnp) +{ + struct cxgbit_device *cdev; + bool found = false; + + mutex_lock(&cdev_list_lock); + list_for_each_entry(cdev, &cdev_list_head, list) { + if (cdev == cnp->com.cdev) { + found = true; + break; + } + } + if (!found) + goto out; + + __cxgbit_free_cdev_np(cdev, cnp); +out: + mutex_unlock(&cdev_list_lock); +} + +void cxgbit_free_np(struct iscsi_np *np) +{ + struct cxgbit_np *cnp = np->np_context; + + cnp->com.state = CSK_STATE_DEAD; + if (cnp->com.cdev) + cxgbit_free_cdev_np(cnp); + else + cxgbit_free_all_np(cnp); + + np->np_context = NULL; + cxgbit_put_cnp(cnp); +} + +static void cxgbit_send_halfclose(struct cxgbit_sock *csk) +{ + struct sk_buff *skb; + struct cpl_close_con_req *req; + unsigned int len = roundup(sizeof(struct cpl_close_con_req), 16); + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) + return; + + req = (struct cpl_close_con_req *)__skb_put(skb, len); + memset(req, 0, len); + + set_wr_txq(skb, CPL_PRIORITY_DATA, csk->txq_idx); + INIT_TP_WR(req, csk->tid); + OPCODE_TID(req) = cpu_to_be32(MK_OPCODE_TID(CPL_CLOSE_CON_REQ, + csk->tid)); + req->rsvd = 0; + + cxgbit_skcb_flags(skb) |= SKCBF_TX_FLAG_COMPL; + __skb_queue_tail(&csk->txq, skb); + cxgbit_push_tx_frames(csk); +} + +static void cxgbit_arp_failure_discard(void *handle, struct sk_buff *skb) +{ + pr_debug("%s cxgbit_device %p\n", __func__, handle); + kfree_skb(skb); +} + +static void cxgbit_abort_arp_failure(void *handle, struct sk_buff *skb) +{ + struct cxgbit_device *cdev = handle; + struct cpl_abort_req *req = cplhdr(skb); + + pr_debug("%s cdev %p\n", __func__, cdev); + req->cmd = CPL_ABORT_NO_RST; + cxgbit_ofld_send(cdev, skb); +} + +static int cxgbit_send_abort_req(struct cxgbit_sock *csk) +{ + struct cpl_abort_req *req; + unsigned int len = roundup(sizeof(*req), 16); + struct sk_buff *skb; + + pr_debug("%s: csk %p tid %u; state %d\n", + __func__, csk, csk->tid, csk->com.state); + + __skb_queue_purge(&csk->txq); + + if (!test_and_set_bit(CSK_TX_DATA_SENT, &csk->com.flags)) + cxgbit_send_tx_flowc_wr(csk); + + skb = __skb_dequeue(&csk->skbq); + req = (struct cpl_abort_req *)__skb_put(skb, len); + memset(req, 0, len); + + set_wr_txq(skb, CPL_PRIORITY_DATA, csk->txq_idx); + t4_set_arp_err_handler(skb, csk->com.cdev, cxgbit_abort_arp_failure); + INIT_TP_WR(req, csk->tid); + OPCODE_TID(req) = cpu_to_be32(MK_OPCODE_TID(CPL_ABORT_REQ, + csk->tid)); + req->cmd = CPL_ABORT_SEND_RST; + return cxgbit_l2t_send(csk->com.cdev, skb, csk->l2t); +} + +void cxgbit_free_conn(struct iscsi_conn *conn) +{ + struct cxgbit_sock *csk = conn->context; + bool release = false; + + pr_debug("%s: state %d\n", + __func__, csk->com.state); + + spin_lock_bh(&csk->lock); + switch (csk->com.state) { + case CSK_STATE_ESTABLISHED: + if (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT) { + csk->com.state = CSK_STATE_CLOSING; + cxgbit_send_halfclose(csk); + } else { + csk->com.state = CSK_STATE_ABORTING; + cxgbit_send_abort_req(csk); + } + break; + case CSK_STATE_CLOSING: + csk->com.state = CSK_STATE_MORIBUND; + cxgbit_send_halfclose(csk); + break; + case CSK_STATE_DEAD: + release = true; + break; + default: + pr_err("%s: csk %p; state %d\n", + __func__, csk, csk->com.state); + } + spin_unlock_bh(&csk->lock); + + if (release) + cxgbit_put_csk(csk); +} + +static void cxgbit_set_emss(struct cxgbit_sock *csk, u16 opt) +{ + csk->emss = csk->com.cdev->lldi.mtus[TCPOPT_MSS_G(opt)] - + ((csk->com.remote_addr.ss_family == AF_INET) ? + sizeof(struct iphdr) : sizeof(struct ipv6hdr)) - + sizeof(struct tcphdr); + csk->mss = csk->emss; + if (TCPOPT_TSTAMP_G(opt)) + csk->emss -= round_up(TCPOLEN_TIMESTAMP, 4); + if (csk->emss < 128) + csk->emss = 128; + if (csk->emss & 7) + pr_info("Warning: misaligned mtu idx %u mss %u emss=%u\n", + TCPOPT_MSS_G(opt), csk->mss, csk->emss); + pr_debug("%s mss_idx %u mss %u emss=%u\n", __func__, TCPOPT_MSS_G(opt), + csk->mss, csk->emss); +} + +static void cxgbit_free_skb(struct cxgbit_sock *csk) +{ + struct sk_buff *skb; + + __skb_queue_purge(&csk->txq); + __skb_queue_purge(&csk->rxq); + __skb_queue_purge(&csk->backlogq); + __skb_queue_purge(&csk->ppodq); + __skb_queue_purge(&csk->skbq); + + while ((skb = cxgbit_sock_dequeue_wr(csk))) + kfree_skb(skb); + + __kfree_skb(csk->lro_hskb); +} + +void _cxgbit_free_csk(struct kref *kref) +{ + struct cxgbit_sock *csk; + struct cxgbit_device *cdev; + + csk = container_of(kref, struct cxgbit_sock, kref); + + pr_debug("%s csk %p state %d\n", __func__, csk, csk->com.state); + + if (csk->com.local_addr.ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) + &csk->com.local_addr; + cxgb4_clip_release(csk->com.cdev->lldi.ports[0], + (const u32 *) + &sin6->sin6_addr.s6_addr, 1); + } + + cxgb4_remove_tid(csk->com.cdev->lldi.tids, 0, csk->tid); + dst_release(csk->dst); + cxgb4_l2t_release(csk->l2t); + + cdev = csk->com.cdev; + spin_lock_bh(&cdev->cskq.lock); + list_del(&csk->list); + spin_unlock_bh(&cdev->cskq.lock); + + cxgbit_free_skb(csk); + cxgbit_put_cdev(cdev); + + kfree(csk); +} + +static void +cxgbit_get_tuple_info(struct cpl_pass_accept_req *req, int *iptype, + __u8 *local_ip, __u8 *peer_ip, __be16 *local_port, + __be16 *peer_port) +{ + u32 eth_len = ETH_HDR_LEN_G(be32_to_cpu(req->hdr_len)); + u32 ip_len = IP_HDR_LEN_G(be32_to_cpu(req->hdr_len)); + struct iphdr *ip = (struct iphdr *)((u8 *)(req + 1) + eth_len); + struct ipv6hdr *ip6 = (struct ipv6hdr *)((u8 *)(req + 1) + eth_len); + struct tcphdr *tcp = (struct tcphdr *) + ((u8 *)(req + 1) + eth_len + ip_len); + + if (ip->version == 4) { + pr_debug("%s saddr 0x%x daddr 0x%x sport %u dport %u\n", + __func__, + ntohl(ip->saddr), ntohl(ip->daddr), + ntohs(tcp->source), + ntohs(tcp->dest)); + *iptype = 4; + memcpy(peer_ip, &ip->saddr, 4); + memcpy(local_ip, &ip->daddr, 4); + } else { + pr_debug("%s saddr %pI6 daddr %pI6 sport %u dport %u\n", + __func__, + ip6->saddr.s6_addr, ip6->daddr.s6_addr, + ntohs(tcp->source), + ntohs(tcp->dest)); + *iptype = 6; + memcpy(peer_ip, ip6->saddr.s6_addr, 16); + memcpy(local_ip, ip6->daddr.s6_addr, 16); + } + + *peer_port = tcp->source; + *local_port = tcp->dest; +} + +static int +cxgbit_our_interface(struct cxgbit_device *cdev, struct net_device *egress_dev) +{ + u8 i; + + egress_dev = cxgbit_get_real_dev(egress_dev); + for (i = 0; i < cdev->lldi.nports; i++) + if (cdev->lldi.ports[i] == egress_dev) + return 1; + return 0; +} + +static struct dst_entry * +cxgbit_find_route6(struct cxgbit_device *cdev, __u8 *local_ip, __u8 *peer_ip, + __be16 local_port, __be16 peer_port, u8 tos, + __u32 sin6_scope_id) +{ + struct dst_entry *dst = NULL; + + if (IS_ENABLED(CONFIG_IPV6)) { + struct flowi6 fl6; + + memset(&fl6, 0, sizeof(fl6)); + memcpy(&fl6.daddr, peer_ip, 16); + memcpy(&fl6.saddr, local_ip, 16); + if (ipv6_addr_type(&fl6.daddr) & IPV6_ADDR_LINKLOCAL) + fl6.flowi6_oif = sin6_scope_id; + dst = ip6_route_output(&init_net, NULL, &fl6); + if (!dst) + goto out; + if (!cxgbit_our_interface(cdev, ip6_dst_idev(dst)->dev) && + !(ip6_dst_idev(dst)->dev->flags & IFF_LOOPBACK)) { + dst_release(dst); + dst = NULL; + } + } +out: + return dst; +} + +static struct dst_entry * +cxgbit_find_route(struct cxgbit_device *cdev, __be32 local_ip, __be32 peer_ip, + __be16 local_port, __be16 peer_port, u8 tos) +{ + struct rtable *rt; + struct flowi4 fl4; + struct neighbour *n; + + rt = ip_route_output_ports(&init_net, &fl4, NULL, peer_ip, + local_ip, + peer_port, local_port, IPPROTO_TCP, + tos, 0); + if (IS_ERR(rt)) + return NULL; + n = dst_neigh_lookup(&rt->dst, &peer_ip); + if (!n) + return NULL; + if (!cxgbit_our_interface(cdev, n->dev) && + !(n->dev->flags & IFF_LOOPBACK)) { + neigh_release(n); + dst_release(&rt->dst); + return NULL; + } + neigh_release(n); + return &rt->dst; +} + +static void cxgbit_set_tcp_window(struct cxgbit_sock *csk, struct port_info *pi) +{ + unsigned int linkspeed; + u8 scale; + + linkspeed = pi->link_cfg.speed; + scale = linkspeed / SPEED_10000; + +#define CXGBIT_10G_RCV_WIN (256 * 1024) + csk->rcv_win = CXGBIT_10G_RCV_WIN; + if (scale) + csk->rcv_win *= scale; + +#define CXGBIT_10G_SND_WIN (256 * 1024) + csk->snd_win = CXGBIT_10G_SND_WIN; + if (scale) + csk->snd_win *= scale; + + pr_debug("%s snd_win %d rcv_win %d\n", + __func__, csk->snd_win, csk->rcv_win); +} + +#ifdef CONFIG_CHELSIO_T4_DCB +static u8 cxgbit_get_iscsi_dcb_state(struct net_device *ndev) +{ + return ndev->dcbnl_ops->getstate(ndev); +} + +static int cxgbit_select_priority(int pri_mask) +{ + if (!pri_mask) + return 0; + + return (ffs(pri_mask) - 1); +} + +static u8 cxgbit_get_iscsi_dcb_priority(struct net_device *ndev, u16 local_port) +{ + int ret; + u8 caps; + + struct dcb_app iscsi_dcb_app = { + .protocol = local_port + }; + + ret = (int)ndev->dcbnl_ops->getcap(ndev, DCB_CAP_ATTR_DCBX, &caps); + + if (ret) + return 0; + + if (caps & DCB_CAP_DCBX_VER_IEEE) { + iscsi_dcb_app.selector = IEEE_8021QAZ_APP_SEL_ANY; + + ret = dcb_ieee_getapp_mask(ndev, &iscsi_dcb_app); + + } else if (caps & DCB_CAP_DCBX_VER_CEE) { + iscsi_dcb_app.selector = DCB_APP_IDTYPE_PORTNUM; + + ret = dcb_getapp(ndev, &iscsi_dcb_app); + } + + pr_info("iSCSI priority is set to %u\n", cxgbit_select_priority(ret)); + + return cxgbit_select_priority(ret); +} +#endif + +static int +cxgbit_offload_init(struct cxgbit_sock *csk, int iptype, __u8 *peer_ip, + u16 local_port, struct dst_entry *dst, + struct cxgbit_device *cdev) +{ + struct neighbour *n; + int ret, step; + struct net_device *ndev; + u16 rxq_idx, port_id; +#ifdef CONFIG_CHELSIO_T4_DCB + u8 priority = 0; +#endif + + n = dst_neigh_lookup(dst, peer_ip); + if (!n) + return -ENODEV; + + rcu_read_lock(); + ret = -ENOMEM; + if (n->dev->flags & IFF_LOOPBACK) { + if (iptype == 4) + ndev = cxgbit_ipv4_netdev(*(__be32 *)peer_ip); + else if (IS_ENABLED(CONFIG_IPV6)) + ndev = cxgbit_ipv6_netdev((struct in6_addr *)peer_ip); + else + ndev = NULL; + + if (!ndev) { + ret = -ENODEV; + goto out; + } + + csk->l2t = cxgb4_l2t_get(cdev->lldi.l2t, + n, ndev, 0); + if (!csk->l2t) + goto out; + csk->mtu = ndev->mtu; + csk->tx_chan = cxgb4_port_chan(ndev); + csk->smac_idx = (cxgb4_port_viid(ndev) & 0x7F) << 1; + step = cdev->lldi.ntxq / + cdev->lldi.nchan; + csk->txq_idx = cxgb4_port_idx(ndev) * step; + step = cdev->lldi.nrxq / + cdev->lldi.nchan; + csk->ctrlq_idx = cxgb4_port_idx(ndev); + csk->rss_qid = cdev->lldi.rxq_ids[ + cxgb4_port_idx(ndev) * step]; + csk->port_id = cxgb4_port_idx(ndev); + cxgbit_set_tcp_window(csk, + (struct port_info *)netdev_priv(ndev)); + } else { + ndev = cxgbit_get_real_dev(n->dev); + if (!ndev) { + ret = -ENODEV; + goto out; + } + +#ifdef CONFIG_CHELSIO_T4_DCB + if (cxgbit_get_iscsi_dcb_state(ndev)) + priority = cxgbit_get_iscsi_dcb_priority(ndev, + local_port); + + csk->dcb_priority = priority; + + csk->l2t = cxgb4_l2t_get(cdev->lldi.l2t, n, ndev, priority); +#else + csk->l2t = cxgb4_l2t_get(cdev->lldi.l2t, n, ndev, 0); +#endif + if (!csk->l2t) + goto out; + port_id = cxgb4_port_idx(ndev); + csk->mtu = dst_mtu(dst); + csk->tx_chan = cxgb4_port_chan(ndev); + csk->smac_idx = (cxgb4_port_viid(ndev) & 0x7F) << 1; + step = cdev->lldi.ntxq / + cdev->lldi.nports; + csk->txq_idx = (port_id * step) + + (cdev->selectq[port_id][0]++ % step); + csk->ctrlq_idx = cxgb4_port_idx(ndev); + step = cdev->lldi.nrxq / + cdev->lldi.nports; + rxq_idx = (port_id * step) + + (cdev->selectq[port_id][1]++ % step); + csk->rss_qid = cdev->lldi.rxq_ids[rxq_idx]; + csk->port_id = port_id; + cxgbit_set_tcp_window(csk, + (struct port_info *)netdev_priv(ndev)); + } + ret = 0; +out: + rcu_read_unlock(); + neigh_release(n); + return ret; +} + +int cxgbit_ofld_send(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + int ret = 0; + + if (!test_bit(CDEV_STATE_UP, &cdev->flags)) { + kfree_skb(skb); + pr_err("%s - device not up - dropping\n", __func__); + return -EIO; + } + + ret = cxgb4_ofld_send(cdev->lldi.ports[0], skb); + if (ret < 0) + kfree_skb(skb); + return ret < 0 ? ret : 0; +} + +static void cxgbit_release_tid(struct cxgbit_device *cdev, u32 tid) +{ + struct cpl_tid_release *req; + unsigned int len = roundup(sizeof(*req), 16); + struct sk_buff *skb; + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) + return; + + req = (struct cpl_tid_release *)__skb_put(skb, len); + memset(req, 0, len); + + INIT_TP_WR(req, tid); + OPCODE_TID(req) = cpu_to_be32(MK_OPCODE_TID( + CPL_TID_RELEASE, tid)); + set_wr_txq(skb, CPL_PRIORITY_SETUP, 0); + cxgbit_ofld_send(cdev, skb); +} + +int +cxgbit_l2t_send(struct cxgbit_device *cdev, struct sk_buff *skb, + struct l2t_entry *l2e) +{ + int ret = 0; + + if (!test_bit(CDEV_STATE_UP, &cdev->flags)) { + kfree_skb(skb); + pr_err("%s - device not up - dropping\n", __func__); + return -EIO; + } + + ret = cxgb4_l2t_send(cdev->lldi.ports[0], skb, l2e); + if (ret < 0) + kfree_skb(skb); + return ret < 0 ? ret : 0; +} + +static void +cxgbit_best_mtu(const unsigned short *mtus, unsigned short mtu, + unsigned int *idx, int use_ts, int ipv6) +{ + unsigned short hdr_size = (ipv6 ? sizeof(struct ipv6hdr) : + sizeof(struct iphdr)) + + sizeof(struct tcphdr) + + (use_ts ? round_up(TCPOLEN_TIMESTAMP, + 4) : 0); + unsigned short data_size = mtu - hdr_size; + + cxgb4_best_aligned_mtu(mtus, hdr_size, data_size, 8, idx); +} + +static void cxgbit_send_rx_credits(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + if (csk->com.state != CSK_STATE_ESTABLISHED) { + __kfree_skb(skb); + return; + } + + cxgbit_ofld_send(csk->com.cdev, skb); +} + +/* + * CPL connection rx data ack: host -> + * Send RX credits through an RX_DATA_ACK CPL message. + * Returns the number of credits sent. + */ +int cxgbit_rx_data_ack(struct cxgbit_sock *csk) +{ + struct sk_buff *skb; + struct cpl_rx_data_ack *req; + unsigned int len = roundup(sizeof(*req), 16); + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return -1; + + req = (struct cpl_rx_data_ack *)__skb_put(skb, len); + memset(req, 0, len); + + set_wr_txq(skb, CPL_PRIORITY_ACK, csk->ctrlq_idx); + INIT_TP_WR(req, csk->tid); + OPCODE_TID(req) = cpu_to_be32(MK_OPCODE_TID(CPL_RX_DATA_ACK, + csk->tid)); + req->credit_dack = cpu_to_be32(RX_DACK_CHANGE_F | RX_DACK_MODE_V(1) | + RX_CREDITS_V(csk->rx_credits)); + + csk->rx_credits = 0; + + spin_lock_bh(&csk->lock); + if (csk->lock_owner) { + cxgbit_skcb_rx_backlog_fn(skb) = cxgbit_send_rx_credits; + __skb_queue_tail(&csk->backlogq, skb); + spin_unlock_bh(&csk->lock); + return 0; + } + + cxgbit_send_rx_credits(csk, skb); + spin_unlock_bh(&csk->lock); + + return 0; +} + +#define FLOWC_WR_NPARAMS_MIN 9 +#define FLOWC_WR_NPARAMS_MAX 11 +static int cxgbit_alloc_csk_skb(struct cxgbit_sock *csk) +{ + struct sk_buff *skb; + u32 len, flowclen; + u8 i; + + flowclen = offsetof(struct fw_flowc_wr, + mnemval[FLOWC_WR_NPARAMS_MAX]); + + len = max_t(u32, sizeof(struct cpl_abort_req), + sizeof(struct cpl_abort_rpl)); + + len = max(len, flowclen); + len = roundup(len, 16); + + for (i = 0; i < 3; i++) { + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) + goto out; + __skb_queue_tail(&csk->skbq, skb); + } + + skb = alloc_skb(LRO_SKB_MIN_HEADROOM, GFP_ATOMIC); + if (!skb) + goto out; + + memset(skb->data, 0, LRO_SKB_MIN_HEADROOM); + csk->lro_hskb = skb; + + return 0; +out: + __skb_queue_purge(&csk->skbq); + return -ENOMEM; +} + +static u32 cxgbit_compute_wscale(u32 win) +{ + u32 wscale = 0; + + while (wscale < 14 && (65535 << wscale) < win) + wscale++; + return wscale; +} + +static void +cxgbit_pass_accept_rpl(struct cxgbit_sock *csk, struct cpl_pass_accept_req *req) +{ + struct sk_buff *skb; + const struct tcphdr *tcph; + struct cpl_t5_pass_accept_rpl *rpl5; + unsigned int len = roundup(sizeof(*rpl5), 16); + unsigned int mtu_idx; + u64 opt0; + u32 opt2, hlen; + u32 wscale; + u32 win; + + pr_debug("%s csk %p tid %u\n", __func__, csk, csk->tid); + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + cxgbit_put_csk(csk); + return; + } + + rpl5 = (struct cpl_t5_pass_accept_rpl *)__skb_put(skb, len); + memset(rpl5, 0, len); + + INIT_TP_WR(rpl5, csk->tid); + OPCODE_TID(rpl5) = cpu_to_be32(MK_OPCODE_TID(CPL_PASS_ACCEPT_RPL, + csk->tid)); + cxgbit_best_mtu(csk->com.cdev->lldi.mtus, csk->mtu, &mtu_idx, + req->tcpopt.tstamp, + (csk->com.remote_addr.ss_family == AF_INET) ? 0 : 1); + wscale = cxgbit_compute_wscale(csk->rcv_win); + /* + * Specify the largest window that will fit in opt0. The + * remainder will be specified in the rx_data_ack. + */ + win = csk->rcv_win >> 10; + if (win > RCV_BUFSIZ_M) + win = RCV_BUFSIZ_M; + opt0 = TCAM_BYPASS_F | + WND_SCALE_V(wscale) | + MSS_IDX_V(mtu_idx) | + L2T_IDX_V(csk->l2t->idx) | + TX_CHAN_V(csk->tx_chan) | + SMAC_SEL_V(csk->smac_idx) | + DSCP_V(csk->tos >> 2) | + ULP_MODE_V(ULP_MODE_ISCSI) | + RCV_BUFSIZ_V(win); + + opt2 = RX_CHANNEL_V(0) | + RSS_QUEUE_VALID_F | RSS_QUEUE_V(csk->rss_qid); + + if (req->tcpopt.tstamp) + opt2 |= TSTAMPS_EN_F; + if (req->tcpopt.sack) + opt2 |= SACK_EN_F; + if (wscale) + opt2 |= WND_SCALE_EN_F; + + hlen = ntohl(req->hdr_len); + tcph = (const void *)(req + 1) + ETH_HDR_LEN_G(hlen) + + IP_HDR_LEN_G(hlen); + + if (tcph->ece && tcph->cwr) + opt2 |= CCTRL_ECN_V(1); + + opt2 |= RX_COALESCE_V(3); + opt2 |= CONG_CNTRL_V(CONG_ALG_NEWRENO); + + opt2 |= T5_ISS_F; + rpl5->iss = cpu_to_be32((prandom_u32() & ~7UL) - 1); + + opt2 |= T5_OPT_2_VALID_F; + + rpl5->opt0 = cpu_to_be64(opt0); + rpl5->opt2 = cpu_to_be32(opt2); + set_wr_txq(skb, CPL_PRIORITY_SETUP, csk->ctrlq_idx); + t4_set_arp_err_handler(skb, NULL, cxgbit_arp_failure_discard); + cxgbit_l2t_send(csk->com.cdev, skb, csk->l2t); +} + +static void +cxgbit_pass_accept_req(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + struct cxgbit_sock *csk = NULL; + struct cxgbit_np *cnp; + struct cpl_pass_accept_req *req = cplhdr(skb); + unsigned int stid = PASS_OPEN_TID_G(ntohl(req->tos_stid)); + struct tid_info *t = cdev->lldi.tids; + unsigned int tid = GET_TID(req); + u16 peer_mss = ntohs(req->tcpopt.mss); + unsigned short hdrs; + + struct dst_entry *dst; + __u8 local_ip[16], peer_ip[16]; + __be16 local_port, peer_port; + int ret; + int iptype; + + pr_debug("%s: cdev = %p; stid = %u; tid = %u\n", + __func__, cdev, stid, tid); + + cnp = lookup_stid(t, stid); + if (!cnp) { + pr_err("%s connect request on invalid stid %d\n", + __func__, stid); + goto rel_skb; + } + + if (cnp->com.state != CSK_STATE_LISTEN) { + pr_err("%s - listening parent not in CSK_STATE_LISTEN\n", + __func__); + goto reject; + } + + csk = lookup_tid(t, tid); + if (csk) { + pr_err("%s csk not null tid %u\n", + __func__, tid); + goto rel_skb; + } + + cxgbit_get_tuple_info(req, &iptype, local_ip, peer_ip, + &local_port, &peer_port); + + /* Find output route */ + if (iptype == 4) { + pr_debug("%s parent sock %p tid %u laddr %pI4 raddr %pI4 " + "lport %d rport %d peer_mss %d\n" + , __func__, cnp, tid, + local_ip, peer_ip, ntohs(local_port), + ntohs(peer_port), peer_mss); + dst = cxgbit_find_route(cdev, *(__be32 *)local_ip, + *(__be32 *)peer_ip, + local_port, peer_port, + PASS_OPEN_TOS_G(ntohl(req->tos_stid))); + } else { + pr_debug("%s parent sock %p tid %u laddr %pI6 raddr %pI6 " + "lport %d rport %d peer_mss %d\n" + , __func__, cnp, tid, + local_ip, peer_ip, ntohs(local_port), + ntohs(peer_port), peer_mss); + dst = cxgbit_find_route6(cdev, local_ip, peer_ip, + local_port, peer_port, + PASS_OPEN_TOS_G(ntohl(req->tos_stid)), + ((struct sockaddr_in6 *) + &cnp->com.local_addr)->sin6_scope_id); + } + if (!dst) { + pr_err("%s - failed to find dst entry!\n", + __func__); + goto reject; + } + + csk = kzalloc(sizeof(*csk), GFP_ATOMIC); + if (!csk) { + dst_release(dst); + goto rel_skb; + } + + ret = cxgbit_offload_init(csk, iptype, peer_ip, ntohs(local_port), + dst, cdev); + if (ret) { + pr_err("%s - failed to allocate l2t entry!\n", + __func__); + dst_release(dst); + kfree(csk); + goto reject; + } + + kref_init(&csk->kref); + init_completion(&csk->com.wr_wait.completion); + + INIT_LIST_HEAD(&csk->accept_node); + + hdrs = (iptype == 4 ? sizeof(struct iphdr) : sizeof(struct ipv6hdr)) + + sizeof(struct tcphdr) + (req->tcpopt.tstamp ? 12 : 0); + if (peer_mss && csk->mtu > (peer_mss + hdrs)) + csk->mtu = peer_mss + hdrs; + + csk->com.state = CSK_STATE_CONNECTING; + csk->com.cdev = cdev; + csk->cnp = cnp; + csk->tos = PASS_OPEN_TOS_G(ntohl(req->tos_stid)); + csk->dst = dst; + csk->tid = tid; + csk->wr_cred = cdev->lldi.wr_cred - + DIV_ROUND_UP(sizeof(struct cpl_abort_req), 16); + csk->wr_max_cred = csk->wr_cred; + csk->wr_una_cred = 0; + + if (iptype == 4) { + struct sockaddr_in *sin = (struct sockaddr_in *) + &csk->com.local_addr; + sin->sin_family = AF_INET; + sin->sin_port = local_port; + sin->sin_addr.s_addr = *(__be32 *)local_ip; + + sin = (struct sockaddr_in *)&csk->com.remote_addr; + sin->sin_family = AF_INET; + sin->sin_port = peer_port; + sin->sin_addr.s_addr = *(__be32 *)peer_ip; + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) + &csk->com.local_addr; + + sin6->sin6_family = PF_INET6; + sin6->sin6_port = local_port; + memcpy(sin6->sin6_addr.s6_addr, local_ip, 16); + cxgb4_clip_get(cdev->lldi.ports[0], + (const u32 *)&sin6->sin6_addr.s6_addr, + 1); + + sin6 = (struct sockaddr_in6 *)&csk->com.remote_addr; + sin6->sin6_family = PF_INET6; + sin6->sin6_port = peer_port; + memcpy(sin6->sin6_addr.s6_addr, peer_ip, 16); + } + + skb_queue_head_init(&csk->rxq); + skb_queue_head_init(&csk->txq); + skb_queue_head_init(&csk->ppodq); + skb_queue_head_init(&csk->backlogq); + skb_queue_head_init(&csk->skbq); + cxgbit_sock_reset_wr_list(csk); + spin_lock_init(&csk->lock); + init_waitqueue_head(&csk->waitq); + init_waitqueue_head(&csk->ack_waitq); + csk->lock_owner = false; + + if (cxgbit_alloc_csk_skb(csk)) { + dst_release(dst); + kfree(csk); + goto rel_skb; + } + + cxgbit_get_cdev(cdev); + + spin_lock(&cdev->cskq.lock); + list_add_tail(&csk->list, &cdev->cskq.list); + spin_unlock(&cdev->cskq.lock); + + cxgb4_insert_tid(t, csk, tid); + cxgbit_pass_accept_rpl(csk, req); + goto rel_skb; + +reject: + cxgbit_release_tid(cdev, tid); +rel_skb: + __kfree_skb(skb); +} + +static u32 +cxgbit_tx_flowc_wr_credits(struct cxgbit_sock *csk, u32 *nparamsp, + u32 *flowclenp) +{ + u32 nparams, flowclen16, flowclen; + + nparams = FLOWC_WR_NPARAMS_MIN; + + if (csk->snd_wscale) + nparams++; + +#ifdef CONFIG_CHELSIO_T4_DCB + nparams++; +#endif + flowclen = offsetof(struct fw_flowc_wr, mnemval[nparams]); + flowclen16 = DIV_ROUND_UP(flowclen, 16); + flowclen = flowclen16 * 16; + /* + * Return the number of 16-byte credits used by the flowc request. + * Pass back the nparams and actual flowc length if requested. + */ + if (nparamsp) + *nparamsp = nparams; + if (flowclenp) + *flowclenp = flowclen; + return flowclen16; +} + +u32 cxgbit_send_tx_flowc_wr(struct cxgbit_sock *csk) +{ + struct cxgbit_device *cdev = csk->com.cdev; + struct fw_flowc_wr *flowc; + u32 nparams, flowclen16, flowclen; + struct sk_buff *skb; + u8 index; + +#ifdef CONFIG_CHELSIO_T4_DCB + u16 vlan = ((struct l2t_entry *)csk->l2t)->vlan; +#endif + + flowclen16 = cxgbit_tx_flowc_wr_credits(csk, &nparams, &flowclen); + + skb = __skb_dequeue(&csk->skbq); + flowc = (struct fw_flowc_wr *)__skb_put(skb, flowclen); + memset(flowc, 0, flowclen); + + flowc->op_to_nparams = cpu_to_be32(FW_WR_OP_V(FW_FLOWC_WR) | + FW_FLOWC_WR_NPARAMS_V(nparams)); + flowc->flowid_len16 = cpu_to_be32(FW_WR_LEN16_V(flowclen16) | + FW_WR_FLOWID_V(csk->tid)); + flowc->mnemval[0].mnemonic = FW_FLOWC_MNEM_PFNVFN; + flowc->mnemval[0].val = cpu_to_be32(FW_PFVF_CMD_PFN_V + (csk->com.cdev->lldi.pf)); + flowc->mnemval[1].mnemonic = FW_FLOWC_MNEM_CH; + flowc->mnemval[1].val = cpu_to_be32(csk->tx_chan); + flowc->mnemval[2].mnemonic = FW_FLOWC_MNEM_PORT; + flowc->mnemval[2].val = cpu_to_be32(csk->tx_chan); + flowc->mnemval[3].mnemonic = FW_FLOWC_MNEM_IQID; + flowc->mnemval[3].val = cpu_to_be32(csk->rss_qid); + flowc->mnemval[4].mnemonic = FW_FLOWC_MNEM_SNDNXT; + flowc->mnemval[4].val = cpu_to_be32(csk->snd_nxt); + flowc->mnemval[5].mnemonic = FW_FLOWC_MNEM_RCVNXT; + flowc->mnemval[5].val = cpu_to_be32(csk->rcv_nxt); + flowc->mnemval[6].mnemonic = FW_FLOWC_MNEM_SNDBUF; + flowc->mnemval[6].val = cpu_to_be32(csk->snd_win); + flowc->mnemval[7].mnemonic = FW_FLOWC_MNEM_MSS; + flowc->mnemval[7].val = cpu_to_be32(csk->emss); + + flowc->mnemval[8].mnemonic = FW_FLOWC_MNEM_TXDATAPLEN_MAX; + if (test_bit(CDEV_ISO_ENABLE, &cdev->flags)) + flowc->mnemval[8].val = cpu_to_be32(CXGBIT_MAX_ISO_PAYLOAD); + else + flowc->mnemval[8].val = cpu_to_be32(16384); + + index = 9; + + if (csk->snd_wscale) { + flowc->mnemval[index].mnemonic = FW_FLOWC_MNEM_RCV_SCALE; + flowc->mnemval[index].val = cpu_to_be32(csk->snd_wscale); + index++; + } + +#ifdef CONFIG_CHELSIO_T4_DCB + flowc->mnemval[index].mnemonic = FW_FLOWC_MNEM_DCBPRIO; + if (vlan == VLAN_NONE) { + pr_warn("csk %u without VLAN Tag on DCB Link\n", csk->tid); + flowc->mnemval[index].val = cpu_to_be32(0); + } else + flowc->mnemval[index].val = cpu_to_be32( + (vlan & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT); +#endif + + pr_debug("%s: csk %p; tx_chan = %u; rss_qid = %u; snd_seq = %u;" + " rcv_seq = %u; snd_win = %u; emss = %u\n", + __func__, csk, csk->tx_chan, csk->rss_qid, csk->snd_nxt, + csk->rcv_nxt, csk->snd_win, csk->emss); + set_wr_txq(skb, CPL_PRIORITY_DATA, csk->txq_idx); + cxgbit_ofld_send(csk->com.cdev, skb); + return flowclen16; +} + +int cxgbit_setup_conn_digest(struct cxgbit_sock *csk) +{ + struct sk_buff *skb; + struct cpl_set_tcb_field *req; + u8 hcrc = csk->submode & CXGBIT_SUBMODE_HCRC; + u8 dcrc = csk->submode & CXGBIT_SUBMODE_DCRC; + unsigned int len = roundup(sizeof(*req), 16); + int ret; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* set up ulp submode */ + req = (struct cpl_set_tcb_field *)__skb_put(skb, len); + memset(req, 0, len); + + INIT_TP_WR(req, csk->tid); + OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, csk->tid)); + req->reply_ctrl = htons(NO_REPLY_V(0) | QUEUENO_V(csk->rss_qid)); + req->word_cookie = htons(0); + req->mask = cpu_to_be64(0x3 << 4); + req->val = cpu_to_be64(((hcrc ? ULP_CRC_HEADER : 0) | + (dcrc ? ULP_CRC_DATA : 0)) << 4); + set_wr_txq(skb, CPL_PRIORITY_CONTROL, csk->ctrlq_idx); + + cxgbit_get_csk(csk); + cxgbit_init_wr_wait(&csk->com.wr_wait); + + cxgbit_ofld_send(csk->com.cdev, skb); + + ret = cxgbit_wait_for_reply(csk->com.cdev, + &csk->com.wr_wait, + csk->tid, 5, __func__); + if (ret) + return -1; + + return 0; +} + +int cxgbit_setup_conn_pgidx(struct cxgbit_sock *csk, u32 pg_idx) +{ + struct sk_buff *skb; + struct cpl_set_tcb_field *req; + unsigned int len = roundup(sizeof(*req), 16); + int ret; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + req = (struct cpl_set_tcb_field *)__skb_put(skb, len); + memset(req, 0, len); + + INIT_TP_WR(req, csk->tid); + OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, csk->tid)); + req->reply_ctrl = htons(NO_REPLY_V(0) | QUEUENO_V(csk->rss_qid)); + req->word_cookie = htons(0); + req->mask = cpu_to_be64(0x3 << 8); + req->val = cpu_to_be64(pg_idx << 8); + set_wr_txq(skb, CPL_PRIORITY_CONTROL, csk->ctrlq_idx); + + cxgbit_get_csk(csk); + cxgbit_init_wr_wait(&csk->com.wr_wait); + + cxgbit_ofld_send(csk->com.cdev, skb); + + ret = cxgbit_wait_for_reply(csk->com.cdev, + &csk->com.wr_wait, + csk->tid, 5, __func__); + if (ret) + return -1; + + return 0; +} + +static void +cxgbit_pass_open_rpl(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + struct cpl_pass_open_rpl *rpl = cplhdr(skb); + struct tid_info *t = cdev->lldi.tids; + unsigned int stid = GET_TID(rpl); + struct cxgbit_np *cnp = lookup_stid(t, stid); + + pr_debug("%s: cnp = %p; stid = %u; status = %d\n", + __func__, cnp, stid, rpl->status); + + if (!cnp) { + pr_info("%s stid %d lookup failure\n", __func__, stid); + return; + } + + cxgbit_wake_up(&cnp->com.wr_wait, __func__, rpl->status); + cxgbit_put_cnp(cnp); +} + +static void +cxgbit_close_listsrv_rpl(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + struct cpl_close_listsvr_rpl *rpl = cplhdr(skb); + struct tid_info *t = cdev->lldi.tids; + unsigned int stid = GET_TID(rpl); + struct cxgbit_np *cnp = lookup_stid(t, stid); + + pr_debug("%s: cnp = %p; stid = %u; status = %d\n", + __func__, cnp, stid, rpl->status); + + if (!cnp) { + pr_info("%s stid %d lookup failure\n", __func__, stid); + return; + } + + cxgbit_wake_up(&cnp->com.wr_wait, __func__, rpl->status); + cxgbit_put_cnp(cnp); +} + +static void +cxgbit_pass_establish(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + struct cpl_pass_establish *req = cplhdr(skb); + struct tid_info *t = cdev->lldi.tids; + unsigned int tid = GET_TID(req); + struct cxgbit_sock *csk; + struct cxgbit_np *cnp; + u16 tcp_opt = be16_to_cpu(req->tcp_opt); + u32 snd_isn = be32_to_cpu(req->snd_isn); + u32 rcv_isn = be32_to_cpu(req->rcv_isn); + + csk = lookup_tid(t, tid); + if (unlikely(!csk)) { + pr_err("can't find connection for tid %u.\n", tid); + goto rel_skb; + } + cnp = csk->cnp; + + pr_debug("%s: csk %p; tid %u; cnp %p\n", + __func__, csk, tid, cnp); + + csk->write_seq = snd_isn; + csk->snd_una = snd_isn; + csk->snd_nxt = snd_isn; + + csk->rcv_nxt = rcv_isn; + + if (csk->rcv_win > (RCV_BUFSIZ_M << 10)) + csk->rx_credits = (csk->rcv_win - (RCV_BUFSIZ_M << 10)); + + csk->snd_wscale = TCPOPT_SND_WSCALE_G(tcp_opt); + cxgbit_set_emss(csk, tcp_opt); + dst_confirm(csk->dst); + csk->com.state = CSK_STATE_ESTABLISHED; + spin_lock_bh(&cnp->np_accept_lock); + list_add_tail(&csk->accept_node, &cnp->np_accept_list); + spin_unlock_bh(&cnp->np_accept_lock); + complete(&cnp->accept_comp); +rel_skb: + __kfree_skb(skb); +} + +static void cxgbit_queue_rx_skb(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + cxgbit_skcb_flags(skb) = 0; + spin_lock_bh(&csk->rxq.lock); + __skb_queue_tail(&csk->rxq, skb); + spin_unlock_bh(&csk->rxq.lock); + wake_up(&csk->waitq); +} + +static void cxgbit_peer_close(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + pr_debug("%s: csk %p; tid %u; state %d\n", + __func__, csk, csk->tid, csk->com.state); + + switch (csk->com.state) { + case CSK_STATE_ESTABLISHED: + csk->com.state = CSK_STATE_CLOSING; + cxgbit_queue_rx_skb(csk, skb); + return; + case CSK_STATE_CLOSING: + /* simultaneous close */ + csk->com.state = CSK_STATE_MORIBUND; + break; + case CSK_STATE_MORIBUND: + csk->com.state = CSK_STATE_DEAD; + cxgbit_put_csk(csk); + break; + case CSK_STATE_ABORTING: + break; + default: + pr_info("%s: cpl_peer_close in bad state %d\n", + __func__, csk->com.state); + } + + __kfree_skb(skb); +} + +static void cxgbit_close_con_rpl(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + pr_debug("%s: csk %p; tid %u; state %d\n", + __func__, csk, csk->tid, csk->com.state); + + switch (csk->com.state) { + case CSK_STATE_CLOSING: + csk->com.state = CSK_STATE_MORIBUND; + break; + case CSK_STATE_MORIBUND: + csk->com.state = CSK_STATE_DEAD; + cxgbit_put_csk(csk); + break; + case CSK_STATE_ABORTING: + case CSK_STATE_DEAD: + break; + default: + pr_info("%s: cpl_close_con_rpl in bad state %d\n", + __func__, csk->com.state); + } + + __kfree_skb(skb); +} + +static void cxgbit_abort_req_rss(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + struct cpl_abort_req_rss *hdr = cplhdr(skb); + unsigned int tid = GET_TID(hdr); + struct cpl_abort_rpl *rpl; + struct sk_buff *rpl_skb; + bool release = false; + bool wakeup_thread = false; + unsigned int len = roundup(sizeof(*rpl), 16); + + pr_debug("%s: csk %p; tid %u; state %d\n", + __func__, csk, tid, csk->com.state); + + if (cxgbit_is_neg_adv(hdr->status)) { + pr_err("%s: got neg advise %d on tid %u\n", + __func__, hdr->status, tid); + goto rel_skb; + } + + switch (csk->com.state) { + case CSK_STATE_CONNECTING: + case CSK_STATE_MORIBUND: + csk->com.state = CSK_STATE_DEAD; + release = true; + break; + case CSK_STATE_ESTABLISHED: + csk->com.state = CSK_STATE_DEAD; + wakeup_thread = true; + break; + case CSK_STATE_CLOSING: + csk->com.state = CSK_STATE_DEAD; + if (!csk->conn) + release = true; + break; + case CSK_STATE_ABORTING: + break; + default: + pr_info("%s: cpl_abort_req_rss in bad state %d\n", + __func__, csk->com.state); + csk->com.state = CSK_STATE_DEAD; + } + + __skb_queue_purge(&csk->txq); + + if (!test_and_set_bit(CSK_TX_DATA_SENT, &csk->com.flags)) + cxgbit_send_tx_flowc_wr(csk); + + rpl_skb = __skb_dequeue(&csk->skbq); + set_wr_txq(skb, CPL_PRIORITY_DATA, csk->txq_idx); + + rpl = (struct cpl_abort_rpl *)__skb_put(rpl_skb, len); + memset(rpl, 0, len); + + INIT_TP_WR(rpl, csk->tid); + OPCODE_TID(rpl) = cpu_to_be32(MK_OPCODE_TID(CPL_ABORT_RPL, tid)); + rpl->cmd = CPL_ABORT_NO_RST; + cxgbit_ofld_send(csk->com.cdev, rpl_skb); + + if (wakeup_thread) { + cxgbit_queue_rx_skb(csk, skb); + return; + } + + if (release) + cxgbit_put_csk(csk); +rel_skb: + __kfree_skb(skb); +} + +static void cxgbit_abort_rpl_rss(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + pr_debug("%s: csk %p; tid %u; state %d\n", + __func__, csk, csk->tid, csk->com.state); + + switch (csk->com.state) { + case CSK_STATE_ABORTING: + csk->com.state = CSK_STATE_DEAD; + cxgbit_put_csk(csk); + break; + default: + pr_info("%s: cpl_abort_rpl_rss in state %d\n", + __func__, csk->com.state); + } + + __kfree_skb(skb); +} + +static bool cxgbit_credit_err(const struct cxgbit_sock *csk) +{ + const struct sk_buff *skb = csk->wr_pending_head; + u32 credit = 0; + + if (unlikely(csk->wr_cred > csk->wr_max_cred)) { + pr_err("csk 0x%p, tid %u, credit %u > %u\n", + csk, csk->tid, csk->wr_cred, csk->wr_max_cred); + return true; + } + + while (skb) { + credit += skb->csum; + skb = cxgbit_skcb_tx_wr_next(skb); + } + + if (unlikely((csk->wr_cred + credit) != csk->wr_max_cred)) { + pr_err("csk 0x%p, tid %u, credit %u + %u != %u.\n", + csk, csk->tid, csk->wr_cred, + credit, csk->wr_max_cred); + + return true; + } + + return false; +} + +static void cxgbit_fw4_ack(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + struct cpl_fw4_ack *rpl = (struct cpl_fw4_ack *)cplhdr(skb); + u32 credits = rpl->credits; + u32 snd_una = ntohl(rpl->snd_una); + + csk->wr_cred += credits; + if (csk->wr_una_cred > (csk->wr_max_cred - csk->wr_cred)) + csk->wr_una_cred = csk->wr_max_cred - csk->wr_cred; + + while (credits) { + struct sk_buff *p = cxgbit_sock_peek_wr(csk); + + if (unlikely(!p)) { + pr_err("csk 0x%p,%u, cr %u,%u+%u, empty.\n", + csk, csk->tid, credits, + csk->wr_cred, csk->wr_una_cred); + break; + } + + if (unlikely(credits < p->csum)) { + pr_warn("csk 0x%p,%u, cr %u,%u+%u, < %u.\n", + csk, csk->tid, + credits, csk->wr_cred, csk->wr_una_cred, + p->csum); + p->csum -= credits; + break; + } + + cxgbit_sock_dequeue_wr(csk); + credits -= p->csum; + kfree_skb(p); + } + + if (unlikely(cxgbit_credit_err(csk))) { + cxgbit_queue_rx_skb(csk, skb); + return; + } + + if (rpl->seq_vld & CPL_FW4_ACK_FLAGS_SEQVAL) { + if (unlikely(before(snd_una, csk->snd_una))) { + pr_warn("csk 0x%p,%u, snd_una %u/%u.", + csk, csk->tid, snd_una, + csk->snd_una); + goto rel_skb; + } + + if (csk->snd_una != snd_una) { + csk->snd_una = snd_una; + dst_confirm(csk->dst); + wake_up(&csk->ack_waitq); + } + } + + if (skb_queue_len(&csk->txq)) + cxgbit_push_tx_frames(csk); + +rel_skb: + __kfree_skb(skb); +} + +static void cxgbit_set_tcb_rpl(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + struct cxgbit_sock *csk; + struct cpl_set_tcb_rpl *rpl = (struct cpl_set_tcb_rpl *)skb->data; + unsigned int tid = GET_TID(rpl); + struct cxgb4_lld_info *lldi = &cdev->lldi; + struct tid_info *t = lldi->tids; + + csk = lookup_tid(t, tid); + if (unlikely(!csk)) + pr_err("can't find connection for tid %u.\n", tid); + else + cxgbit_wake_up(&csk->com.wr_wait, __func__, rpl->status); + + cxgbit_put_csk(csk); +} + +static void cxgbit_rx_data(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + struct cxgbit_sock *csk; + struct cpl_rx_data *cpl = cplhdr(skb); + unsigned int tid = GET_TID(cpl); + struct cxgb4_lld_info *lldi = &cdev->lldi; + struct tid_info *t = lldi->tids; + + csk = lookup_tid(t, tid); + if (unlikely(!csk)) { + pr_err("can't find conn. for tid %u.\n", tid); + goto rel_skb; + } + + cxgbit_queue_rx_skb(csk, skb); + return; +rel_skb: + __kfree_skb(skb); +} + +static void +__cxgbit_process_rx_cpl(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + spin_lock(&csk->lock); + if (csk->lock_owner) { + __skb_queue_tail(&csk->backlogq, skb); + spin_unlock(&csk->lock); + return; + } + + cxgbit_skcb_rx_backlog_fn(skb)(csk, skb); + spin_unlock(&csk->lock); +} + +static void cxgbit_process_rx_cpl(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + cxgbit_get_csk(csk); + __cxgbit_process_rx_cpl(csk, skb); + cxgbit_put_csk(csk); +} + +static void cxgbit_rx_cpl(struct cxgbit_device *cdev, struct sk_buff *skb) +{ + struct cxgbit_sock *csk; + struct cpl_tx_data *cpl = cplhdr(skb); + struct cxgb4_lld_info *lldi = &cdev->lldi; + struct tid_info *t = lldi->tids; + unsigned int tid = GET_TID(cpl); + u8 opcode = cxgbit_skcb_rx_opcode(skb); + bool ref = true; + + switch (opcode) { + case CPL_FW4_ACK: + cxgbit_skcb_rx_backlog_fn(skb) = cxgbit_fw4_ack; + ref = false; + break; + case CPL_PEER_CLOSE: + cxgbit_skcb_rx_backlog_fn(skb) = cxgbit_peer_close; + break; + case CPL_CLOSE_CON_RPL: + cxgbit_skcb_rx_backlog_fn(skb) = cxgbit_close_con_rpl; + break; + case CPL_ABORT_REQ_RSS: + cxgbit_skcb_rx_backlog_fn(skb) = cxgbit_abort_req_rss; + break; + case CPL_ABORT_RPL_RSS: + cxgbit_skcb_rx_backlog_fn(skb) = cxgbit_abort_rpl_rss; + break; + default: + goto rel_skb; + } + + csk = lookup_tid(t, tid); + if (unlikely(!csk)) { + pr_err("can't find conn. for tid %u.\n", tid); + goto rel_skb; + } + + if (ref) + cxgbit_process_rx_cpl(csk, skb); + else + __cxgbit_process_rx_cpl(csk, skb); + + return; +rel_skb: + __kfree_skb(skb); +} + +cxgbit_cplhandler_func cxgbit_cplhandlers[NUM_CPL_CMDS] = { + [CPL_PASS_OPEN_RPL] = cxgbit_pass_open_rpl, + [CPL_CLOSE_LISTSRV_RPL] = cxgbit_close_listsrv_rpl, + [CPL_PASS_ACCEPT_REQ] = cxgbit_pass_accept_req, + [CPL_PASS_ESTABLISH] = cxgbit_pass_establish, + [CPL_SET_TCB_RPL] = cxgbit_set_tcb_rpl, + [CPL_RX_DATA] = cxgbit_rx_data, + [CPL_FW4_ACK] = cxgbit_rx_cpl, + [CPL_PEER_CLOSE] = cxgbit_rx_cpl, + [CPL_CLOSE_CON_RPL] = cxgbit_rx_cpl, + [CPL_ABORT_REQ_RSS] = cxgbit_rx_cpl, + [CPL_ABORT_RPL_RSS] = cxgbit_rx_cpl, +}; diff --git a/drivers/target/iscsi/cxgbit/cxgbit_ddp.c b/drivers/target/iscsi/cxgbit/cxgbit_ddp.c new file mode 100644 index 000000000000..5d78bdb7fc64 --- /dev/null +++ b/drivers/target/iscsi/cxgbit/cxgbit_ddp.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2016 Chelsio Communications, Inc. + * + * 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. + */ + +#include "cxgbit.h" + +static void +cxgbit_set_one_ppod(struct cxgbi_pagepod *ppod, + struct cxgbi_task_tag_info *ttinfo, + struct scatterlist **sg_pp, unsigned int *sg_off) +{ + struct scatterlist *sg = sg_pp ? *sg_pp : NULL; + unsigned int offset = sg_off ? *sg_off : 0; + dma_addr_t addr = 0UL; + unsigned int len = 0; + int i; + + memcpy(ppod, &ttinfo->hdr, sizeof(struct cxgbi_pagepod_hdr)); + + if (sg) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + } + + for (i = 0; i < PPOD_PAGES_MAX; i++) { + if (sg) { + ppod->addr[i] = cpu_to_be64(addr + offset); + offset += PAGE_SIZE; + if (offset == (len + sg->offset)) { + offset = 0; + sg = sg_next(sg); + if (sg) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + } + } + } else { + ppod->addr[i] = 0ULL; + } + } + + /* + * the fifth address needs to be repeated in the next ppod, so do + * not move sg + */ + if (sg_pp) { + *sg_pp = sg; + *sg_off = offset; + } + + if (offset == len) { + offset = 0; + if (sg) { + sg = sg_next(sg); + if (sg) + addr = sg_dma_address(sg); + } + } + ppod->addr[i] = sg ? cpu_to_be64(addr + offset) : 0ULL; +} + +static struct sk_buff * +cxgbit_ppod_init_idata(struct cxgbit_device *cdev, struct cxgbi_ppm *ppm, + unsigned int idx, unsigned int npods, unsigned int tid) +{ + struct ulp_mem_io *req; + struct ulptx_idata *idata; + unsigned int pm_addr = (idx << PPOD_SIZE_SHIFT) + ppm->llimit; + unsigned int dlen = npods << PPOD_SIZE_SHIFT; + unsigned int wr_len = roundup(sizeof(struct ulp_mem_io) + + sizeof(struct ulptx_idata) + dlen, 16); + struct sk_buff *skb; + + skb = alloc_skb(wr_len, GFP_KERNEL); + if (!skb) + return NULL; + + req = (struct ulp_mem_io *)__skb_put(skb, wr_len); + INIT_ULPTX_WR(req, wr_len, 0, tid); + req->wr.wr_hi = htonl(FW_WR_OP_V(FW_ULPTX_WR) | + FW_WR_ATOMIC_V(0)); + req->cmd = htonl(ULPTX_CMD_V(ULP_TX_MEM_WRITE) | + ULP_MEMIO_ORDER_V(0) | + T5_ULP_MEMIO_IMM_V(1)); + req->dlen = htonl(ULP_MEMIO_DATA_LEN_V(dlen >> 5)); + req->lock_addr = htonl(ULP_MEMIO_ADDR_V(pm_addr >> 5)); + req->len16 = htonl(DIV_ROUND_UP(wr_len - sizeof(req->wr), 16)); + + idata = (struct ulptx_idata *)(req + 1); + idata->cmd_more = htonl(ULPTX_CMD_V(ULP_TX_SC_IMM)); + idata->len = htonl(dlen); + + return skb; +} + +static int +cxgbit_ppod_write_idata(struct cxgbi_ppm *ppm, struct cxgbit_sock *csk, + struct cxgbi_task_tag_info *ttinfo, unsigned int idx, + unsigned int npods, struct scatterlist **sg_pp, + unsigned int *sg_off) +{ + struct cxgbit_device *cdev = csk->com.cdev; + struct sk_buff *skb; + struct ulp_mem_io *req; + struct ulptx_idata *idata; + struct cxgbi_pagepod *ppod; + unsigned int i; + + skb = cxgbit_ppod_init_idata(cdev, ppm, idx, npods, csk->tid); + if (!skb) + return -ENOMEM; + + req = (struct ulp_mem_io *)skb->data; + idata = (struct ulptx_idata *)(req + 1); + ppod = (struct cxgbi_pagepod *)(idata + 1); + + for (i = 0; i < npods; i++, ppod++) + cxgbit_set_one_ppod(ppod, ttinfo, sg_pp, sg_off); + + __skb_queue_tail(&csk->ppodq, skb); + + return 0; +} + +static int +cxgbit_ddp_set_map(struct cxgbi_ppm *ppm, struct cxgbit_sock *csk, + struct cxgbi_task_tag_info *ttinfo) +{ + unsigned int pidx = ttinfo->idx; + unsigned int npods = ttinfo->npods; + unsigned int i, cnt; + struct scatterlist *sg = ttinfo->sgl; + unsigned int offset = 0; + int ret = 0; + + for (i = 0; i < npods; i += cnt, pidx += cnt) { + cnt = npods - i; + + if (cnt > ULPMEM_IDATA_MAX_NPPODS) + cnt = ULPMEM_IDATA_MAX_NPPODS; + + ret = cxgbit_ppod_write_idata(ppm, csk, ttinfo, pidx, cnt, + &sg, &offset); + if (ret < 0) + break; + } + + return ret; +} + +static int cxgbit_ddp_sgl_check(struct scatterlist *sg, + unsigned int nents) +{ + unsigned int last_sgidx = nents - 1; + unsigned int i; + + for (i = 0; i < nents; i++, sg = sg_next(sg)) { + unsigned int len = sg->length + sg->offset; + + if ((sg->offset & 0x3) || (i && sg->offset) || + ((i != last_sgidx) && (len != PAGE_SIZE))) { + return -EINVAL; + } + } + + return 0; +} + +static int +cxgbit_ddp_reserve(struct cxgbit_sock *csk, struct cxgbi_task_tag_info *ttinfo, + unsigned int xferlen) +{ + struct cxgbit_device *cdev = csk->com.cdev; + struct cxgbi_ppm *ppm = cdev2ppm(cdev); + struct scatterlist *sgl = ttinfo->sgl; + unsigned int sgcnt = ttinfo->nents; + unsigned int sg_offset = sgl->offset; + int ret; + + if ((xferlen < DDP_THRESHOLD) || (!sgcnt)) { + pr_debug("ppm 0x%p, pgidx %u, xfer %u, sgcnt %u, NO ddp.\n", + ppm, ppm->tformat.pgsz_idx_dflt, + xferlen, ttinfo->nents); + return -EINVAL; + } + + if (cxgbit_ddp_sgl_check(sgl, sgcnt) < 0) + return -EINVAL; + + ttinfo->nr_pages = (xferlen + sgl->offset + + (1 << PAGE_SHIFT) - 1) >> PAGE_SHIFT; + + /* + * the ddp tag will be used for the ttt in the outgoing r2t pdu + */ + ret = cxgbi_ppm_ppods_reserve(ppm, ttinfo->nr_pages, 0, &ttinfo->idx, + &ttinfo->tag, 0); + if (ret < 0) + return ret; + ttinfo->npods = ret; + + sgl->offset = 0; + ret = dma_map_sg(&ppm->pdev->dev, sgl, sgcnt, DMA_FROM_DEVICE); + sgl->offset = sg_offset; + if (!ret) { + pr_info("%s: 0x%x, xfer %u, sgl %u dma mapping err.\n", + __func__, 0, xferlen, sgcnt); + goto rel_ppods; + } + + cxgbi_ppm_make_ppod_hdr(ppm, ttinfo->tag, csk->tid, sgl->offset, + xferlen, &ttinfo->hdr); + + ret = cxgbit_ddp_set_map(ppm, csk, ttinfo); + if (ret < 0) { + __skb_queue_purge(&csk->ppodq); + dma_unmap_sg(&ppm->pdev->dev, sgl, sgcnt, DMA_FROM_DEVICE); + goto rel_ppods; + } + + return 0; + +rel_ppods: + cxgbi_ppm_ppod_release(ppm, ttinfo->idx); + return -EINVAL; +} + +void +cxgbit_get_r2t_ttt(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_r2t *r2t) +{ + struct cxgbit_sock *csk = conn->context; + struct cxgbit_device *cdev = csk->com.cdev; + struct cxgbit_cmd *ccmd = iscsit_priv_cmd(cmd); + struct cxgbi_task_tag_info *ttinfo = &ccmd->ttinfo; + int ret = -EINVAL; + + if ((!ccmd->setup_ddp) || + (!test_bit(CSK_DDP_ENABLE, &csk->com.flags))) + goto out; + + ccmd->setup_ddp = false; + + ttinfo->sgl = cmd->se_cmd.t_data_sg; + ttinfo->nents = cmd->se_cmd.t_data_nents; + + ret = cxgbit_ddp_reserve(csk, ttinfo, cmd->se_cmd.data_length); + if (ret < 0) { + pr_info("csk 0x%p, cmd 0x%p, xfer len %u, sgcnt %u no ddp.\n", + csk, cmd, cmd->se_cmd.data_length, ttinfo->nents); + + ttinfo->sgl = NULL; + ttinfo->nents = 0; + } else { + ccmd->release = true; + } +out: + pr_debug("cdev 0x%p, cmd 0x%p, tag 0x%x\n", cdev, cmd, ttinfo->tag); + r2t->targ_xfer_tag = ttinfo->tag; +} + +void cxgbit_release_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +{ + struct cxgbit_cmd *ccmd = iscsit_priv_cmd(cmd); + + if (ccmd->release) { + struct cxgbi_task_tag_info *ttinfo = &ccmd->ttinfo; + + if (ttinfo->sgl) { + struct cxgbit_sock *csk = conn->context; + struct cxgbit_device *cdev = csk->com.cdev; + struct cxgbi_ppm *ppm = cdev2ppm(cdev); + + cxgbi_ppm_ppod_release(ppm, ttinfo->idx); + + dma_unmap_sg(&ppm->pdev->dev, ttinfo->sgl, + ttinfo->nents, DMA_FROM_DEVICE); + } else { + put_page(sg_page(&ccmd->sg)); + } + + ccmd->release = false; + } +} + +int cxgbit_ddp_init(struct cxgbit_device *cdev) +{ + struct cxgb4_lld_info *lldi = &cdev->lldi; + struct net_device *ndev = cdev->lldi.ports[0]; + struct cxgbi_tag_format tformat; + unsigned int ppmax; + int ret, i; + + if (!lldi->vr->iscsi.size) { + pr_warn("%s, iscsi NOT enabled, check config!\n", ndev->name); + return -EACCES; + } + + ppmax = lldi->vr->iscsi.size >> PPOD_SIZE_SHIFT; + + memset(&tformat, 0, sizeof(struct cxgbi_tag_format)); + for (i = 0; i < 4; i++) + tformat.pgsz_order[i] = (lldi->iscsi_pgsz_order >> (i << 3)) + & 0xF; + cxgbi_tagmask_check(lldi->iscsi_tagmask, &tformat); + + ret = cxgbi_ppm_init(lldi->iscsi_ppm, cdev->lldi.ports[0], + cdev->lldi.pdev, &cdev->lldi, &tformat, + ppmax, lldi->iscsi_llimit, + lldi->vr->iscsi.start, 2); + if (ret >= 0) { + struct cxgbi_ppm *ppm = (struct cxgbi_ppm *)(*lldi->iscsi_ppm); + + if ((ppm->tformat.pgsz_idx_dflt < DDP_PGIDX_MAX) && + (ppm->ppmax >= 1024)) + set_bit(CDEV_DDP_ENABLE, &cdev->flags); + ret = 0; + } + + return ret; +} diff --git a/drivers/target/iscsi/cxgbit/cxgbit_lro.h b/drivers/target/iscsi/cxgbit/cxgbit_lro.h new file mode 100644 index 000000000000..28c11bd1b930 --- /dev/null +++ b/drivers/target/iscsi/cxgbit/cxgbit_lro.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016 Chelsio Communications, Inc. + * + * 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. + * + */ + +#ifndef __CXGBIT_LRO_H__ +#define __CXGBIT_LRO_H__ + +#include +#include +#include +#include +#include + +#define LRO_FLUSH_LEN_MAX 65535 + +struct cxgbit_lro_cb { + struct cxgbit_sock *csk; + u32 pdu_totallen; + u32 offset; + u8 pdu_idx; + bool complete; +}; + +enum cxgbit_pducb_flags { + PDUCBF_RX_HDR = (1 << 0), /* received pdu header */ + PDUCBF_RX_DATA = (1 << 1), /* received pdu payload */ + PDUCBF_RX_STATUS = (1 << 2), /* received ddp status */ + PDUCBF_RX_DATA_DDPD = (1 << 3), /* pdu payload ddp'd */ + PDUCBF_RX_HCRC_ERR = (1 << 4), /* header digest error */ + PDUCBF_RX_DCRC_ERR = (1 << 5), /* data digest error */ +}; + +struct cxgbit_lro_pdu_cb { + u8 flags; + u8 frags; + u8 hfrag_idx; + u8 nr_dfrags; + u8 dfrag_idx; + bool complete; + u32 seq; + u32 pdulen; + u32 hlen; + u32 dlen; + u32 doffset; + u32 ddigest; + void *hdr; +}; + +#define LRO_SKB_MAX_HEADROOM \ + (sizeof(struct cxgbit_lro_cb) + \ + (MAX_SKB_FRAGS * sizeof(struct cxgbit_lro_pdu_cb))) + +#define LRO_SKB_MIN_HEADROOM \ + (sizeof(struct cxgbit_lro_cb) + \ + sizeof(struct cxgbit_lro_pdu_cb)) + +#define cxgbit_skb_lro_cb(skb) ((struct cxgbit_lro_cb *)skb->data) +#define cxgbit_skb_lro_pdu_cb(skb, i) \ + ((struct cxgbit_lro_pdu_cb *)(skb->data + sizeof(struct cxgbit_lro_cb) \ + + (i * sizeof(struct cxgbit_lro_pdu_cb)))) + +#define CPL_RX_ISCSI_DDP_STATUS_DDP_SHIFT 16 /* ddp'able */ +#define CPL_RX_ISCSI_DDP_STATUS_PAD_SHIFT 19 /* pad error */ +#define CPL_RX_ISCSI_DDP_STATUS_HCRC_SHIFT 20 /* hcrc error */ +#define CPL_RX_ISCSI_DDP_STATUS_DCRC_SHIFT 21 /* dcrc error */ + +#endif /*__CXGBIT_LRO_H_*/ diff --git a/drivers/target/iscsi/cxgbit/cxgbit_main.c b/drivers/target/iscsi/cxgbit/cxgbit_main.c new file mode 100644 index 000000000000..39162987d009 --- /dev/null +++ b/drivers/target/iscsi/cxgbit/cxgbit_main.c @@ -0,0 +1,701 @@ +/* + * Copyright (c) 2016 Chelsio Communications, Inc. + * + * 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. + */ + +#define DRV_NAME "cxgbit" +#define DRV_VERSION "1.0.0-ko" +#define pr_fmt(fmt) DRV_NAME ": " fmt + +#include "cxgbit.h" + +#ifdef CONFIG_CHELSIO_T4_DCB +#include +#include "cxgb4_dcb.h" +#endif + +LIST_HEAD(cdev_list_head); +/* cdev list lock */ +DEFINE_MUTEX(cdev_list_lock); + +void _cxgbit_free_cdev(struct kref *kref) +{ + struct cxgbit_device *cdev; + + cdev = container_of(kref, struct cxgbit_device, kref); + kfree(cdev); +} + +static void cxgbit_set_mdsl(struct cxgbit_device *cdev) +{ + struct cxgb4_lld_info *lldi = &cdev->lldi; + u32 mdsl; + +#define ULP2_MAX_PKT_LEN 16224 +#define ISCSI_PDU_NONPAYLOAD_LEN 312 + mdsl = min_t(u32, lldi->iscsi_iolen - ISCSI_PDU_NONPAYLOAD_LEN, + ULP2_MAX_PKT_LEN - ISCSI_PDU_NONPAYLOAD_LEN); + mdsl = min_t(u32, mdsl, 8192); + mdsl = min_t(u32, mdsl, (MAX_SKB_FRAGS - 1) * PAGE_SIZE); + + cdev->mdsl = mdsl; +} + +static void *cxgbit_uld_add(const struct cxgb4_lld_info *lldi) +{ + struct cxgbit_device *cdev; + + if (is_t4(lldi->adapter_type)) + return ERR_PTR(-ENODEV); + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return ERR_PTR(-ENOMEM); + + kref_init(&cdev->kref); + + cdev->lldi = *lldi; + + cxgbit_set_mdsl(cdev); + + if (cxgbit_ddp_init(cdev) < 0) { + kfree(cdev); + return ERR_PTR(-EINVAL); + } + + if (!test_bit(CDEV_DDP_ENABLE, &cdev->flags)) + pr_info("cdev %s ddp init failed\n", + pci_name(lldi->pdev)); + + if (lldi->fw_vers >= 0x10d2b00) + set_bit(CDEV_ISO_ENABLE, &cdev->flags); + + spin_lock_init(&cdev->cskq.lock); + INIT_LIST_HEAD(&cdev->cskq.list); + + mutex_lock(&cdev_list_lock); + list_add_tail(&cdev->list, &cdev_list_head); + mutex_unlock(&cdev_list_lock); + + pr_info("cdev %s added for iSCSI target transport\n", + pci_name(lldi->pdev)); + + return cdev; +} + +static void cxgbit_close_conn(struct cxgbit_device *cdev) +{ + struct cxgbit_sock *csk; + struct sk_buff *skb; + bool wakeup_thread = false; + + spin_lock_bh(&cdev->cskq.lock); + list_for_each_entry(csk, &cdev->cskq.list, list) { + skb = alloc_skb(0, GFP_ATOMIC); + if (!skb) + continue; + + spin_lock_bh(&csk->rxq.lock); + __skb_queue_tail(&csk->rxq, skb); + if (skb_queue_len(&csk->rxq) == 1) + wakeup_thread = true; + spin_unlock_bh(&csk->rxq.lock); + + if (wakeup_thread) { + wake_up(&csk->waitq); + wakeup_thread = false; + } + } + spin_unlock_bh(&cdev->cskq.lock); +} + +static void cxgbit_detach_cdev(struct cxgbit_device *cdev) +{ + bool free_cdev = false; + + spin_lock_bh(&cdev->cskq.lock); + if (list_empty(&cdev->cskq.list)) + free_cdev = true; + spin_unlock_bh(&cdev->cskq.lock); + + if (free_cdev) { + mutex_lock(&cdev_list_lock); + list_del(&cdev->list); + mutex_unlock(&cdev_list_lock); + + cxgbit_put_cdev(cdev); + } else { + cxgbit_close_conn(cdev); + } +} + +static int cxgbit_uld_state_change(void *handle, enum cxgb4_state state) +{ + struct cxgbit_device *cdev = handle; + + switch (state) { + case CXGB4_STATE_UP: + set_bit(CDEV_STATE_UP, &cdev->flags); + pr_info("cdev %s state UP.\n", pci_name(cdev->lldi.pdev)); + break; + case CXGB4_STATE_START_RECOVERY: + clear_bit(CDEV_STATE_UP, &cdev->flags); + cxgbit_close_conn(cdev); + pr_info("cdev %s state RECOVERY.\n", pci_name(cdev->lldi.pdev)); + break; + case CXGB4_STATE_DOWN: + pr_info("cdev %s state DOWN.\n", pci_name(cdev->lldi.pdev)); + break; + case CXGB4_STATE_DETACH: + clear_bit(CDEV_STATE_UP, &cdev->flags); + pr_info("cdev %s state DETACH.\n", pci_name(cdev->lldi.pdev)); + cxgbit_detach_cdev(cdev); + break; + default: + pr_info("cdev %s unknown state %d.\n", + pci_name(cdev->lldi.pdev), state); + break; + } + return 0; +} + +static void +cxgbit_proc_ddp_status(unsigned int tid, struct cpl_rx_data_ddp *cpl, + struct cxgbit_lro_pdu_cb *pdu_cb) +{ + unsigned int status = ntohl(cpl->ddpvld); + + pdu_cb->flags |= PDUCBF_RX_STATUS; + pdu_cb->ddigest = ntohl(cpl->ulp_crc); + pdu_cb->pdulen = ntohs(cpl->len); + + if (status & (1 << CPL_RX_ISCSI_DDP_STATUS_HCRC_SHIFT)) { + pr_info("tid 0x%x, status 0x%x, hcrc bad.\n", tid, status); + pdu_cb->flags |= PDUCBF_RX_HCRC_ERR; + } + + if (status & (1 << CPL_RX_ISCSI_DDP_STATUS_DCRC_SHIFT)) { + pr_info("tid 0x%x, status 0x%x, dcrc bad.\n", tid, status); + pdu_cb->flags |= PDUCBF_RX_DCRC_ERR; + } + + if (status & (1 << CPL_RX_ISCSI_DDP_STATUS_PAD_SHIFT)) + pr_info("tid 0x%x, status 0x%x, pad bad.\n", tid, status); + + if ((status & (1 << CPL_RX_ISCSI_DDP_STATUS_DDP_SHIFT)) && + (!(pdu_cb->flags & PDUCBF_RX_DATA))) { + pdu_cb->flags |= PDUCBF_RX_DATA_DDPD; + } +} + +static void +cxgbit_lro_add_packet_rsp(struct sk_buff *skb, u8 op, const __be64 *rsp) +{ + struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb); + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb, + lro_cb->pdu_idx); + struct cpl_rx_iscsi_ddp *cpl = (struct cpl_rx_iscsi_ddp *)(rsp + 1); + + cxgbit_proc_ddp_status(lro_cb->csk->tid, cpl, pdu_cb); + + if (pdu_cb->flags & PDUCBF_RX_HDR) + pdu_cb->complete = true; + + lro_cb->complete = true; + lro_cb->pdu_totallen += pdu_cb->pdulen; + lro_cb->pdu_idx++; +} + +static void +cxgbit_copy_frags(struct sk_buff *skb, const struct pkt_gl *gl, + unsigned int offset) +{ + u8 skb_frag_idx = skb_shinfo(skb)->nr_frags; + u8 i; + + /* usually there's just one frag */ + __skb_fill_page_desc(skb, skb_frag_idx, gl->frags[0].page, + gl->frags[0].offset + offset, + gl->frags[0].size - offset); + for (i = 1; i < gl->nfrags; i++) + __skb_fill_page_desc(skb, skb_frag_idx + i, + gl->frags[i].page, + gl->frags[i].offset, + gl->frags[i].size); + + skb_shinfo(skb)->nr_frags += gl->nfrags; + + /* get a reference to the last page, we don't own it */ + get_page(gl->frags[gl->nfrags - 1].page); +} + +static void +cxgbit_lro_add_packet_gl(struct sk_buff *skb, u8 op, const struct pkt_gl *gl) +{ + struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb); + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb, + lro_cb->pdu_idx); + u32 len, offset; + + if (op == CPL_ISCSI_HDR) { + struct cpl_iscsi_hdr *cpl = (struct cpl_iscsi_hdr *)gl->va; + + offset = sizeof(struct cpl_iscsi_hdr); + pdu_cb->flags |= PDUCBF_RX_HDR; + pdu_cb->seq = ntohl(cpl->seq); + len = ntohs(cpl->len); + pdu_cb->hdr = gl->va + offset; + pdu_cb->hlen = len; + pdu_cb->hfrag_idx = skb_shinfo(skb)->nr_frags; + + if (unlikely(gl->nfrags > 1)) + cxgbit_skcb_flags(skb) = 0; + + lro_cb->complete = false; + } else { + struct cpl_iscsi_data *cpl = (struct cpl_iscsi_data *)gl->va; + + offset = sizeof(struct cpl_iscsi_data); + pdu_cb->flags |= PDUCBF_RX_DATA; + len = ntohs(cpl->len); + pdu_cb->dlen = len; + pdu_cb->doffset = lro_cb->offset; + pdu_cb->nr_dfrags = gl->nfrags; + pdu_cb->dfrag_idx = skb_shinfo(skb)->nr_frags; + } + + cxgbit_copy_frags(skb, gl, offset); + + pdu_cb->frags += gl->nfrags; + lro_cb->offset += len; + skb->len += len; + skb->data_len += len; + skb->truesize += len; +} + +static struct sk_buff * +cxgbit_lro_init_skb(struct cxgbit_sock *csk, u8 op, const struct pkt_gl *gl, + const __be64 *rsp, struct napi_struct *napi) +{ + struct sk_buff *skb; + struct cxgbit_lro_cb *lro_cb; + + skb = napi_alloc_skb(napi, LRO_SKB_MAX_HEADROOM); + + if (unlikely(!skb)) + return NULL; + + memset(skb->data, 0, LRO_SKB_MAX_HEADROOM); + + cxgbit_skcb_flags(skb) |= SKCBF_RX_LRO; + + lro_cb = cxgbit_skb_lro_cb(skb); + + cxgbit_get_csk(csk); + + lro_cb->csk = csk; + + return skb; +} + +static void cxgbit_queue_lro_skb(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + bool wakeup_thread = false; + + spin_lock(&csk->rxq.lock); + __skb_queue_tail(&csk->rxq, skb); + if (skb_queue_len(&csk->rxq) == 1) + wakeup_thread = true; + spin_unlock(&csk->rxq.lock); + + if (wakeup_thread) + wake_up(&csk->waitq); +} + +static void cxgbit_lro_flush(struct t4_lro_mgr *lro_mgr, struct sk_buff *skb) +{ + struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb); + struct cxgbit_sock *csk = lro_cb->csk; + + csk->lro_skb = NULL; + + __skb_unlink(skb, &lro_mgr->lroq); + cxgbit_queue_lro_skb(csk, skb); + + cxgbit_put_csk(csk); + + lro_mgr->lro_pkts++; + lro_mgr->lro_session_cnt--; +} + +static void cxgbit_uld_lro_flush(struct t4_lro_mgr *lro_mgr) +{ + struct sk_buff *skb; + + while ((skb = skb_peek(&lro_mgr->lroq))) + cxgbit_lro_flush(lro_mgr, skb); +} + +static int +cxgbit_lro_receive(struct cxgbit_sock *csk, u8 op, const __be64 *rsp, + const struct pkt_gl *gl, struct t4_lro_mgr *lro_mgr, + struct napi_struct *napi) +{ + struct sk_buff *skb; + struct cxgbit_lro_cb *lro_cb; + + if (!csk) { + pr_err("%s: csk NULL, op 0x%x.\n", __func__, op); + goto out; + } + + if (csk->lro_skb) + goto add_packet; + +start_lro: + if (lro_mgr->lro_session_cnt >= MAX_LRO_SESSIONS) { + cxgbit_uld_lro_flush(lro_mgr); + goto start_lro; + } + + skb = cxgbit_lro_init_skb(csk, op, gl, rsp, napi); + if (unlikely(!skb)) + goto out; + + csk->lro_skb = skb; + + __skb_queue_tail(&lro_mgr->lroq, skb); + lro_mgr->lro_session_cnt++; + +add_packet: + skb = csk->lro_skb; + lro_cb = cxgbit_skb_lro_cb(skb); + + if ((gl && (((skb_shinfo(skb)->nr_frags + gl->nfrags) > + MAX_SKB_FRAGS) || (lro_cb->pdu_totallen >= LRO_FLUSH_LEN_MAX))) || + (lro_cb->pdu_idx >= MAX_SKB_FRAGS)) { + cxgbit_lro_flush(lro_mgr, skb); + goto start_lro; + } + + if (gl) + cxgbit_lro_add_packet_gl(skb, op, gl); + else + cxgbit_lro_add_packet_rsp(skb, op, rsp); + + lro_mgr->lro_merged++; + + return 0; + +out: + return -1; +} + +static int +cxgbit_uld_lro_rx_handler(void *hndl, const __be64 *rsp, + const struct pkt_gl *gl, struct t4_lro_mgr *lro_mgr, + struct napi_struct *napi) +{ + struct cxgbit_device *cdev = hndl; + struct cxgb4_lld_info *lldi = &cdev->lldi; + struct cpl_tx_data *rpl = NULL; + struct cxgbit_sock *csk = NULL; + unsigned int tid = 0; + struct sk_buff *skb; + unsigned int op = *(u8 *)rsp; + bool lro_flush = true; + + switch (op) { + case CPL_ISCSI_HDR: + case CPL_ISCSI_DATA: + case CPL_RX_ISCSI_DDP: + case CPL_FW4_ACK: + lro_flush = false; + case CPL_ABORT_RPL_RSS: + case CPL_PASS_ESTABLISH: + case CPL_PEER_CLOSE: + case CPL_CLOSE_CON_RPL: + case CPL_ABORT_REQ_RSS: + case CPL_SET_TCB_RPL: + case CPL_RX_DATA: + rpl = gl ? (struct cpl_tx_data *)gl->va : + (struct cpl_tx_data *)(rsp + 1); + tid = GET_TID(rpl); + csk = lookup_tid(lldi->tids, tid); + break; + default: + break; + } + + if (csk && csk->lro_skb && lro_flush) + cxgbit_lro_flush(lro_mgr, csk->lro_skb); + + if (!gl) { + unsigned int len; + + if (op == CPL_RX_ISCSI_DDP) { + if (!cxgbit_lro_receive(csk, op, rsp, NULL, lro_mgr, + napi)) + return 0; + } + + len = 64 - sizeof(struct rsp_ctrl) - 8; + skb = napi_alloc_skb(napi, len); + if (!skb) + goto nomem; + __skb_put(skb, len); + skb_copy_to_linear_data(skb, &rsp[1], len); + } else { + if (unlikely(op != *(u8 *)gl->va)) { + pr_info("? FL 0x%p,RSS%#llx,FL %#llx,len %u.\n", + gl->va, be64_to_cpu(*rsp), + be64_to_cpu(*(u64 *)gl->va), + gl->tot_len); + return 0; + } + + if (op == CPL_ISCSI_HDR || op == CPL_ISCSI_DATA) { + if (!cxgbit_lro_receive(csk, op, rsp, gl, lro_mgr, + napi)) + return 0; + } + +#define RX_PULL_LEN 128 + skb = cxgb4_pktgl_to_skb(gl, RX_PULL_LEN, RX_PULL_LEN); + if (unlikely(!skb)) + goto nomem; + } + + rpl = (struct cpl_tx_data *)skb->data; + op = rpl->ot.opcode; + cxgbit_skcb_rx_opcode(skb) = op; + + pr_debug("cdev %p, opcode 0x%x(0x%x,0x%x), skb %p.\n", + cdev, op, rpl->ot.opcode_tid, + ntohl(rpl->ot.opcode_tid), skb); + + if (op < NUM_CPL_CMDS && cxgbit_cplhandlers[op]) { + cxgbit_cplhandlers[op](cdev, skb); + } else { + pr_err("No handler for opcode 0x%x.\n", op); + __kfree_skb(skb); + } + return 0; +nomem: + pr_err("%s OOM bailing out.\n", __func__); + return 1; +} + +#ifdef CONFIG_CHELSIO_T4_DCB +struct cxgbit_dcb_work { + struct dcb_app_type dcb_app; + struct work_struct work; +}; + +static void +cxgbit_update_dcb_priority(struct cxgbit_device *cdev, u8 port_id, + u8 dcb_priority, u16 port_num) +{ + struct cxgbit_sock *csk; + struct sk_buff *skb; + u16 local_port; + bool wakeup_thread = false; + + spin_lock_bh(&cdev->cskq.lock); + list_for_each_entry(csk, &cdev->cskq.list, list) { + if (csk->port_id != port_id) + continue; + + if (csk->com.local_addr.ss_family == AF_INET6) { + struct sockaddr_in6 *sock_in6; + + sock_in6 = (struct sockaddr_in6 *)&csk->com.local_addr; + local_port = ntohs(sock_in6->sin6_port); + } else { + struct sockaddr_in *sock_in; + + sock_in = (struct sockaddr_in *)&csk->com.local_addr; + local_port = ntohs(sock_in->sin_port); + } + + if (local_port != port_num) + continue; + + if (csk->dcb_priority == dcb_priority) + continue; + + skb = alloc_skb(0, GFP_ATOMIC); + if (!skb) + continue; + + spin_lock(&csk->rxq.lock); + __skb_queue_tail(&csk->rxq, skb); + if (skb_queue_len(&csk->rxq) == 1) + wakeup_thread = true; + spin_unlock(&csk->rxq.lock); + + if (wakeup_thread) { + wake_up(&csk->waitq); + wakeup_thread = false; + } + } + spin_unlock_bh(&cdev->cskq.lock); +} + +static void cxgbit_dcb_workfn(struct work_struct *work) +{ + struct cxgbit_dcb_work *dcb_work; + struct net_device *ndev; + struct cxgbit_device *cdev = NULL; + struct dcb_app_type *iscsi_app; + u8 priority, port_id = 0xff; + + dcb_work = container_of(work, struct cxgbit_dcb_work, work); + iscsi_app = &dcb_work->dcb_app; + + if (iscsi_app->dcbx & DCB_CAP_DCBX_VER_IEEE) { + if (iscsi_app->app.selector != IEEE_8021QAZ_APP_SEL_ANY) + goto out; + + priority = iscsi_app->app.priority; + + } else if (iscsi_app->dcbx & DCB_CAP_DCBX_VER_CEE) { + if (iscsi_app->app.selector != DCB_APP_IDTYPE_PORTNUM) + goto out; + + if (!iscsi_app->app.priority) + goto out; + + priority = ffs(iscsi_app->app.priority) - 1; + } else { + goto out; + } + + pr_debug("priority for ifid %d is %u\n", + iscsi_app->ifindex, priority); + + ndev = dev_get_by_index(&init_net, iscsi_app->ifindex); + + if (!ndev) + goto out; + + mutex_lock(&cdev_list_lock); + cdev = cxgbit_find_device(ndev, &port_id); + + dev_put(ndev); + + if (!cdev) { + mutex_unlock(&cdev_list_lock); + goto out; + } + + cxgbit_update_dcb_priority(cdev, port_id, priority, + iscsi_app->app.protocol); + mutex_unlock(&cdev_list_lock); +out: + kfree(dcb_work); +} + +static int +cxgbit_dcbevent_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct cxgbit_dcb_work *dcb_work; + struct dcb_app_type *dcb_app = data; + + dcb_work = kzalloc(sizeof(*dcb_work), GFP_ATOMIC); + if (!dcb_work) + return NOTIFY_DONE; + + dcb_work->dcb_app = *dcb_app; + INIT_WORK(&dcb_work->work, cxgbit_dcb_workfn); + schedule_work(&dcb_work->work); + return NOTIFY_OK; +} +#endif + +static enum target_prot_op cxgbit_get_sup_prot_ops(struct iscsi_conn *conn) +{ + return TARGET_PROT_NORMAL; +} + +static struct iscsit_transport cxgbit_transport = { + .name = DRV_NAME, + .transport_type = ISCSI_HW_OFFLOAD, + .priv_size = sizeof(struct cxgbit_cmd), + .owner = THIS_MODULE, + .iscsit_setup_np = cxgbit_setup_np, + .iscsit_accept_np = cxgbit_accept_np, + .iscsit_free_np = cxgbit_free_np, + .iscsit_free_conn = cxgbit_free_conn, + .iscsit_get_login_rx = cxgbit_get_login_rx, + .iscsit_put_login_tx = cxgbit_put_login_tx, + .iscsit_immediate_queue = iscsit_immediate_queue, + .iscsit_response_queue = iscsit_response_queue, + .iscsit_get_dataout = iscsit_build_r2ts_for_cmd, + .iscsit_queue_data_in = iscsit_queue_rsp, + .iscsit_queue_status = iscsit_queue_rsp, + .iscsit_xmit_pdu = cxgbit_xmit_pdu, + .iscsit_get_r2t_ttt = cxgbit_get_r2t_ttt, + .iscsit_get_rx_pdu = cxgbit_get_rx_pdu, + .iscsit_validate_params = cxgbit_validate_params, + .iscsit_release_cmd = cxgbit_release_cmd, + .iscsit_aborted_task = iscsit_aborted_task, + .iscsit_get_sup_prot_ops = cxgbit_get_sup_prot_ops, +}; + +static struct cxgb4_uld_info cxgbit_uld_info = { + .name = DRV_NAME, + .add = cxgbit_uld_add, + .state_change = cxgbit_uld_state_change, + .lro_rx_handler = cxgbit_uld_lro_rx_handler, + .lro_flush = cxgbit_uld_lro_flush, +}; + +#ifdef CONFIG_CHELSIO_T4_DCB +static struct notifier_block cxgbit_dcbevent_nb = { + .notifier_call = cxgbit_dcbevent_notify, +}; +#endif + +static int __init cxgbit_init(void) +{ + cxgb4_register_uld(CXGB4_ULD_ISCSIT, &cxgbit_uld_info); + iscsit_register_transport(&cxgbit_transport); + +#ifdef CONFIG_CHELSIO_T4_DCB + pr_info("%s dcb enabled.\n", DRV_NAME); + register_dcbevent_notifier(&cxgbit_dcbevent_nb); +#endif + BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, cb) < + sizeof(union cxgbit_skb_cb)); + return 0; +} + +static void __exit cxgbit_exit(void) +{ + struct cxgbit_device *cdev, *tmp; + +#ifdef CONFIG_CHELSIO_T4_DCB + unregister_dcbevent_notifier(&cxgbit_dcbevent_nb); +#endif + mutex_lock(&cdev_list_lock); + list_for_each_entry_safe(cdev, tmp, &cdev_list_head, list) { + list_del(&cdev->list); + cxgbit_put_cdev(cdev); + } + mutex_unlock(&cdev_list_lock); + iscsit_unregister_transport(&cxgbit_transport); + cxgb4_unregister_uld(CXGB4_ULD_ISCSIT); +} + +module_init(cxgbit_init); +module_exit(cxgbit_exit); + +MODULE_DESCRIPTION("Chelsio iSCSI target offload driver"); +MODULE_AUTHOR("Chelsio Communications"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/target/iscsi/cxgbit/cxgbit_target.c b/drivers/target/iscsi/cxgbit/cxgbit_target.c new file mode 100644 index 000000000000..d02bf58aea6d --- /dev/null +++ b/drivers/target/iscsi/cxgbit/cxgbit_target.c @@ -0,0 +1,1561 @@ +/* + * Copyright (c) 2016 Chelsio Communications, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include "cxgbit.h" + +struct sge_opaque_hdr { + void *dev; + dma_addr_t addr[MAX_SKB_FRAGS + 1]; +}; + +static const u8 cxgbit_digest_len[] = {0, 4, 4, 8}; + +#define TX_HDR_LEN (sizeof(struct sge_opaque_hdr) + \ + sizeof(struct fw_ofld_tx_data_wr)) + +static struct sk_buff * +__cxgbit_alloc_skb(struct cxgbit_sock *csk, u32 len, bool iso) +{ + struct sk_buff *skb = NULL; + u8 submode = 0; + int errcode; + static const u32 hdr_len = TX_HDR_LEN + ISCSI_HDR_LEN; + + if (len) { + skb = alloc_skb_with_frags(hdr_len, len, + 0, &errcode, + GFP_KERNEL); + if (!skb) + return NULL; + + skb_reserve(skb, TX_HDR_LEN); + skb_reset_transport_header(skb); + __skb_put(skb, ISCSI_HDR_LEN); + skb->data_len = len; + skb->len += len; + submode |= (csk->submode & CXGBIT_SUBMODE_DCRC); + + } else { + u32 iso_len = iso ? sizeof(struct cpl_tx_data_iso) : 0; + + skb = alloc_skb(hdr_len + iso_len, GFP_KERNEL); + if (!skb) + return NULL; + + skb_reserve(skb, TX_HDR_LEN + iso_len); + skb_reset_transport_header(skb); + __skb_put(skb, ISCSI_HDR_LEN); + } + + submode |= (csk->submode & CXGBIT_SUBMODE_HCRC); + cxgbit_skcb_submode(skb) = submode; + cxgbit_skcb_tx_extralen(skb) = cxgbit_digest_len[submode]; + cxgbit_skcb_flags(skb) |= SKCBF_TX_NEED_HDR; + return skb; +} + +static struct sk_buff *cxgbit_alloc_skb(struct cxgbit_sock *csk, u32 len) +{ + return __cxgbit_alloc_skb(csk, len, false); +} + +/* + * cxgbit_is_ofld_imm - check whether a packet can be sent as immediate data + * @skb: the packet + * + * Returns true if a packet can be sent as an offload WR with immediate + * data. We currently use the same limit as for Ethernet packets. + */ +static int cxgbit_is_ofld_imm(const struct sk_buff *skb) +{ + int length = skb->len; + + if (likely(cxgbit_skcb_flags(skb) & SKCBF_TX_NEED_HDR)) + length += sizeof(struct fw_ofld_tx_data_wr); + + if (likely(cxgbit_skcb_flags(skb) & SKCBF_TX_ISO)) + length += sizeof(struct cpl_tx_data_iso); + +#define MAX_IMM_TX_PKT_LEN 256 + return length <= MAX_IMM_TX_PKT_LEN; +} + +/* + * cxgbit_sgl_len - calculates the size of an SGL of the given capacity + * @n: the number of SGL entries + * Calculates the number of flits needed for a scatter/gather list that + * can hold the given number of entries. + */ +static inline unsigned int cxgbit_sgl_len(unsigned int n) +{ + n--; + return (3 * n) / 2 + (n & 1) + 2; +} + +/* + * cxgbit_calc_tx_flits_ofld - calculate # of flits for an offload packet + * @skb: the packet + * + * Returns the number of flits needed for the given offload packet. + * These packets are already fully constructed and no additional headers + * will be added. + */ +static unsigned int cxgbit_calc_tx_flits_ofld(const struct sk_buff *skb) +{ + unsigned int flits, cnt; + + if (cxgbit_is_ofld_imm(skb)) + return DIV_ROUND_UP(skb->len, 8); + flits = skb_transport_offset(skb) / 8; + cnt = skb_shinfo(skb)->nr_frags; + if (skb_tail_pointer(skb) != skb_transport_header(skb)) + cnt++; + return flits + cxgbit_sgl_len(cnt); +} + +#define CXGBIT_ISO_FSLICE 0x1 +#define CXGBIT_ISO_LSLICE 0x2 +static void +cxgbit_cpl_tx_data_iso(struct sk_buff *skb, struct cxgbit_iso_info *iso_info) +{ + struct cpl_tx_data_iso *cpl; + unsigned int submode = cxgbit_skcb_submode(skb); + unsigned int fslice = !!(iso_info->flags & CXGBIT_ISO_FSLICE); + unsigned int lslice = !!(iso_info->flags & CXGBIT_ISO_LSLICE); + + cpl = (struct cpl_tx_data_iso *)__skb_push(skb, sizeof(*cpl)); + + cpl->op_to_scsi = htonl(CPL_TX_DATA_ISO_OP_V(CPL_TX_DATA_ISO) | + CPL_TX_DATA_ISO_FIRST_V(fslice) | + CPL_TX_DATA_ISO_LAST_V(lslice) | + CPL_TX_DATA_ISO_CPLHDRLEN_V(0) | + CPL_TX_DATA_ISO_HDRCRC_V(submode & 1) | + CPL_TX_DATA_ISO_PLDCRC_V(((submode >> 1) & 1)) | + CPL_TX_DATA_ISO_IMMEDIATE_V(0) | + CPL_TX_DATA_ISO_SCSI_V(2)); + + cpl->ahs_len = 0; + cpl->mpdu = htons(DIV_ROUND_UP(iso_info->mpdu, 4)); + cpl->burst_size = htonl(DIV_ROUND_UP(iso_info->burst_len, 4)); + cpl->len = htonl(iso_info->len); + cpl->reserved2_seglen_offset = htonl(0); + cpl->datasn_offset = htonl(0); + cpl->buffer_offset = htonl(0); + cpl->reserved3 = 0; + + __skb_pull(skb, sizeof(*cpl)); +} + +static void +cxgbit_tx_data_wr(struct cxgbit_sock *csk, struct sk_buff *skb, u32 dlen, + u32 len, u32 credits, u32 compl) +{ + struct fw_ofld_tx_data_wr *req; + u32 submode = cxgbit_skcb_submode(skb); + u32 wr_ulp_mode = 0; + u32 hdr_size = sizeof(*req); + u32 opcode = FW_OFLD_TX_DATA_WR; + u32 immlen = 0; + u32 force = TX_FORCE_V(!submode); + + if (cxgbit_skcb_flags(skb) & SKCBF_TX_ISO) { + opcode = FW_ISCSI_TX_DATA_WR; + immlen += sizeof(struct cpl_tx_data_iso); + hdr_size += sizeof(struct cpl_tx_data_iso); + submode |= 8; + } + + if (cxgbit_is_ofld_imm(skb)) + immlen += dlen; + + req = (struct fw_ofld_tx_data_wr *)__skb_push(skb, + hdr_size); + req->op_to_immdlen = cpu_to_be32(FW_WR_OP_V(opcode) | + FW_WR_COMPL_V(compl) | + FW_WR_IMMDLEN_V(immlen)); + req->flowid_len16 = cpu_to_be32(FW_WR_FLOWID_V(csk->tid) | + FW_WR_LEN16_V(credits)); + req->plen = htonl(len); + wr_ulp_mode = FW_OFLD_TX_DATA_WR_ULPMODE_V(ULP_MODE_ISCSI) | + FW_OFLD_TX_DATA_WR_ULPSUBMODE_V(submode); + + req->tunnel_to_proxy = htonl((wr_ulp_mode) | force | + FW_OFLD_TX_DATA_WR_SHOVE_V(skb_peek(&csk->txq) ? 0 : 1)); +} + +static void cxgbit_arp_failure_skb_discard(void *handle, struct sk_buff *skb) +{ + kfree_skb(skb); +} + +void cxgbit_push_tx_frames(struct cxgbit_sock *csk) +{ + struct sk_buff *skb; + + while (csk->wr_cred && ((skb = skb_peek(&csk->txq)) != NULL)) { + u32 dlen = skb->len; + u32 len = skb->len; + u32 credits_needed; + u32 compl = 0; + u32 flowclen16 = 0; + u32 iso_cpl_len = 0; + + if (cxgbit_skcb_flags(skb) & SKCBF_TX_ISO) + iso_cpl_len = sizeof(struct cpl_tx_data_iso); + + if (cxgbit_is_ofld_imm(skb)) + credits_needed = DIV_ROUND_UP(dlen + iso_cpl_len, 16); + else + credits_needed = DIV_ROUND_UP((8 * + cxgbit_calc_tx_flits_ofld(skb)) + + iso_cpl_len, 16); + + if (likely(cxgbit_skcb_flags(skb) & SKCBF_TX_NEED_HDR)) + credits_needed += DIV_ROUND_UP( + sizeof(struct fw_ofld_tx_data_wr), 16); + /* + * Assumes the initial credits is large enough to support + * fw_flowc_wr plus largest possible first payload + */ + + if (!test_and_set_bit(CSK_TX_DATA_SENT, &csk->com.flags)) { + flowclen16 = cxgbit_send_tx_flowc_wr(csk); + csk->wr_cred -= flowclen16; + csk->wr_una_cred += flowclen16; + } + + if (csk->wr_cred < credits_needed) { + pr_debug("csk 0x%p, skb %u/%u, wr %d < %u.\n", + csk, skb->len, skb->data_len, + credits_needed, csk->wr_cred); + break; + } + __skb_unlink(skb, &csk->txq); + set_wr_txq(skb, CPL_PRIORITY_DATA, csk->txq_idx); + skb->csum = credits_needed + flowclen16; + csk->wr_cred -= credits_needed; + csk->wr_una_cred += credits_needed; + + pr_debug("csk 0x%p, skb %u/%u, wr %d, left %u, unack %u.\n", + csk, skb->len, skb->data_len, credits_needed, + csk->wr_cred, csk->wr_una_cred); + + if (likely(cxgbit_skcb_flags(skb) & SKCBF_TX_NEED_HDR)) { + len += cxgbit_skcb_tx_extralen(skb); + + if ((csk->wr_una_cred >= (csk->wr_max_cred / 2)) || + (!before(csk->write_seq, + csk->snd_una + csk->snd_win))) { + compl = 1; + csk->wr_una_cred = 0; + } + + cxgbit_tx_data_wr(csk, skb, dlen, len, credits_needed, + compl); + csk->snd_nxt += len; + + } else if ((cxgbit_skcb_flags(skb) & SKCBF_TX_FLAG_COMPL) || + (csk->wr_una_cred >= (csk->wr_max_cred / 2))) { + struct cpl_close_con_req *req = + (struct cpl_close_con_req *)skb->data; + req->wr.wr_hi |= htonl(FW_WR_COMPL_F); + csk->wr_una_cred = 0; + } + + cxgbit_sock_enqueue_wr(csk, skb); + t4_set_arp_err_handler(skb, csk, + cxgbit_arp_failure_skb_discard); + + pr_debug("csk 0x%p,%u, skb 0x%p, %u.\n", + csk, csk->tid, skb, len); + + cxgbit_l2t_send(csk->com.cdev, skb, csk->l2t); + } +} + +static bool cxgbit_lock_sock(struct cxgbit_sock *csk) +{ + spin_lock_bh(&csk->lock); + + if (before(csk->write_seq, csk->snd_una + csk->snd_win)) + csk->lock_owner = true; + + spin_unlock_bh(&csk->lock); + + return csk->lock_owner; +} + +static void cxgbit_unlock_sock(struct cxgbit_sock *csk) +{ + struct sk_buff_head backlogq; + struct sk_buff *skb; + void (*fn)(struct cxgbit_sock *, struct sk_buff *); + + skb_queue_head_init(&backlogq); + + spin_lock_bh(&csk->lock); + while (skb_queue_len(&csk->backlogq)) { + skb_queue_splice_init(&csk->backlogq, &backlogq); + spin_unlock_bh(&csk->lock); + + while ((skb = __skb_dequeue(&backlogq))) { + fn = cxgbit_skcb_rx_backlog_fn(skb); + fn(csk, skb); + } + + spin_lock_bh(&csk->lock); + } + + csk->lock_owner = false; + spin_unlock_bh(&csk->lock); +} + +static int cxgbit_queue_skb(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + int ret = 0; + + wait_event_interruptible(csk->ack_waitq, cxgbit_lock_sock(csk)); + + if (unlikely((csk->com.state != CSK_STATE_ESTABLISHED) || + signal_pending(current))) { + __kfree_skb(skb); + __skb_queue_purge(&csk->ppodq); + ret = -1; + spin_lock_bh(&csk->lock); + if (csk->lock_owner) { + spin_unlock_bh(&csk->lock); + goto unlock; + } + spin_unlock_bh(&csk->lock); + return ret; + } + + csk->write_seq += skb->len + + cxgbit_skcb_tx_extralen(skb); + + skb_queue_splice_tail_init(&csk->ppodq, &csk->txq); + __skb_queue_tail(&csk->txq, skb); + cxgbit_push_tx_frames(csk); + +unlock: + cxgbit_unlock_sock(csk); + return ret; +} + +static int +cxgbit_map_skb(struct iscsi_cmd *cmd, struct sk_buff *skb, u32 data_offset, + u32 data_length) +{ + u32 i = 0, nr_frags = MAX_SKB_FRAGS; + u32 padding = ((-data_length) & 3); + struct scatterlist *sg; + struct page *page; + unsigned int page_off; + + if (padding) + nr_frags--; + + /* + * We know each entry in t_data_sg contains a page. + */ + sg = &cmd->se_cmd.t_data_sg[data_offset / PAGE_SIZE]; + page_off = (data_offset % PAGE_SIZE); + + while (data_length && (i < nr_frags)) { + u32 cur_len = min_t(u32, data_length, sg->length - page_off); + + page = sg_page(sg); + + get_page(page); + skb_fill_page_desc(skb, i, page, sg->offset + page_off, + cur_len); + skb->data_len += cur_len; + skb->len += cur_len; + skb->truesize += cur_len; + + data_length -= cur_len; + page_off = 0; + sg = sg_next(sg); + i++; + } + + if (data_length) + return -1; + + if (padding) { + page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!page) + return -1; + skb_fill_page_desc(skb, i, page, 0, padding); + skb->data_len += padding; + skb->len += padding; + skb->truesize += padding; + } + + return 0; +} + +static int +cxgbit_tx_datain_iso(struct cxgbit_sock *csk, struct iscsi_cmd *cmd, + struct iscsi_datain_req *dr) +{ + struct iscsi_conn *conn = csk->conn; + struct sk_buff *skb; + struct iscsi_datain datain; + struct cxgbit_iso_info iso_info; + u32 data_length = cmd->se_cmd.data_length; + u32 mrdsl = conn->conn_ops->MaxRecvDataSegmentLength; + u32 num_pdu, plen, tx_data = 0; + bool task_sense = !!(cmd->se_cmd.se_cmd_flags & + SCF_TRANSPORT_TASK_SENSE); + bool set_statsn = false; + int ret = -1; + + while (data_length) { + num_pdu = (data_length + mrdsl - 1) / mrdsl; + if (num_pdu > csk->max_iso_npdu) + num_pdu = csk->max_iso_npdu; + + plen = num_pdu * mrdsl; + if (plen > data_length) + plen = data_length; + + skb = __cxgbit_alloc_skb(csk, 0, true); + if (unlikely(!skb)) + return -ENOMEM; + + memset(skb->data, 0, ISCSI_HDR_LEN); + cxgbit_skcb_flags(skb) |= SKCBF_TX_ISO; + cxgbit_skcb_submode(skb) |= (csk->submode & + CXGBIT_SUBMODE_DCRC); + cxgbit_skcb_tx_extralen(skb) = (num_pdu * + cxgbit_digest_len[cxgbit_skcb_submode(skb)]) + + ((num_pdu - 1) * ISCSI_HDR_LEN); + + memset(&datain, 0, sizeof(struct iscsi_datain)); + memset(&iso_info, 0, sizeof(iso_info)); + + if (!tx_data) + iso_info.flags |= CXGBIT_ISO_FSLICE; + + if (!(data_length - plen)) { + iso_info.flags |= CXGBIT_ISO_LSLICE; + if (!task_sense) { + datain.flags = ISCSI_FLAG_DATA_STATUS; + iscsit_increment_maxcmdsn(cmd, conn->sess); + cmd->stat_sn = conn->stat_sn++; + set_statsn = true; + } + } + + iso_info.burst_len = num_pdu * mrdsl; + iso_info.mpdu = mrdsl; + iso_info.len = ISCSI_HDR_LEN + plen; + + cxgbit_cpl_tx_data_iso(skb, &iso_info); + + datain.offset = tx_data; + datain.data_sn = cmd->data_sn - 1; + + iscsit_build_datain_pdu(cmd, conn, &datain, + (struct iscsi_data_rsp *)skb->data, + set_statsn); + + ret = cxgbit_map_skb(cmd, skb, tx_data, plen); + if (unlikely(ret)) { + __kfree_skb(skb); + goto out; + } + + ret = cxgbit_queue_skb(csk, skb); + if (unlikely(ret)) + goto out; + + tx_data += plen; + data_length -= plen; + + cmd->read_data_done += plen; + cmd->data_sn += num_pdu; + } + + dr->dr_complete = DATAIN_COMPLETE_NORMAL; + + return 0; + +out: + return ret; +} + +static int +cxgbit_tx_datain(struct cxgbit_sock *csk, struct iscsi_cmd *cmd, + const struct iscsi_datain *datain) +{ + struct sk_buff *skb; + int ret = 0; + + skb = cxgbit_alloc_skb(csk, 0); + if (unlikely(!skb)) + return -ENOMEM; + + memcpy(skb->data, cmd->pdu, ISCSI_HDR_LEN); + + if (datain->length) { + cxgbit_skcb_submode(skb) |= (csk->submode & + CXGBIT_SUBMODE_DCRC); + cxgbit_skcb_tx_extralen(skb) = + cxgbit_digest_len[cxgbit_skcb_submode(skb)]; + } + + ret = cxgbit_map_skb(cmd, skb, datain->offset, datain->length); + if (ret < 0) { + __kfree_skb(skb); + return ret; + } + + return cxgbit_queue_skb(csk, skb); +} + +static int +cxgbit_xmit_datain_pdu(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_datain_req *dr, + const struct iscsi_datain *datain) +{ + struct cxgbit_sock *csk = conn->context; + u32 data_length = cmd->se_cmd.data_length; + u32 padding = ((-data_length) & 3); + u32 mrdsl = conn->conn_ops->MaxRecvDataSegmentLength; + + if ((data_length > mrdsl) && (!dr->recovery) && + (!padding) && (!datain->offset) && csk->max_iso_npdu) { + atomic_long_add(data_length - datain->length, + &conn->sess->tx_data_octets); + return cxgbit_tx_datain_iso(csk, cmd, dr); + } + + return cxgbit_tx_datain(csk, cmd, datain); +} + +static int +cxgbit_xmit_nondatain_pdu(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + const void *data_buf, u32 data_buf_len) +{ + struct cxgbit_sock *csk = conn->context; + struct sk_buff *skb; + u32 padding = ((-data_buf_len) & 3); + + skb = cxgbit_alloc_skb(csk, data_buf_len + padding); + if (unlikely(!skb)) + return -ENOMEM; + + memcpy(skb->data, cmd->pdu, ISCSI_HDR_LEN); + + if (data_buf_len) { + u32 pad_bytes = 0; + + skb_store_bits(skb, ISCSI_HDR_LEN, data_buf, data_buf_len); + + if (padding) + skb_store_bits(skb, ISCSI_HDR_LEN + data_buf_len, + &pad_bytes, padding); + } + + cxgbit_skcb_tx_extralen(skb) = cxgbit_digest_len[ + cxgbit_skcb_submode(skb)]; + + return cxgbit_queue_skb(csk, skb); +} + +int +cxgbit_xmit_pdu(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_datain_req *dr, const void *buf, u32 buf_len) +{ + if (dr) + return cxgbit_xmit_datain_pdu(conn, cmd, dr, buf); + else + return cxgbit_xmit_nondatain_pdu(conn, cmd, buf, buf_len); +} + +int cxgbit_validate_params(struct iscsi_conn *conn) +{ + struct cxgbit_sock *csk = conn->context; + struct cxgbit_device *cdev = csk->com.cdev; + struct iscsi_param *param; + u32 max_xmitdsl; + + param = iscsi_find_param_from_key(MAXXMITDATASEGMENTLENGTH, + conn->param_list); + if (!param) + return -1; + + if (kstrtou32(param->value, 0, &max_xmitdsl) < 0) + return -1; + + if (max_xmitdsl > cdev->mdsl) { + if (iscsi_change_param_sprintf( + conn, "MaxXmitDataSegmentLength=%u", cdev->mdsl)) + return -1; + } + + return 0; +} + +static int cxgbit_set_digest(struct cxgbit_sock *csk) +{ + struct iscsi_conn *conn = csk->conn; + struct iscsi_param *param; + + param = iscsi_find_param_from_key(HEADERDIGEST, conn->param_list); + if (!param) { + pr_err("param not found key %s\n", HEADERDIGEST); + return -1; + } + + if (!strcmp(param->value, CRC32C)) + csk->submode |= CXGBIT_SUBMODE_HCRC; + + param = iscsi_find_param_from_key(DATADIGEST, conn->param_list); + if (!param) { + csk->submode = 0; + pr_err("param not found key %s\n", DATADIGEST); + return -1; + } + + if (!strcmp(param->value, CRC32C)) + csk->submode |= CXGBIT_SUBMODE_DCRC; + + if (cxgbit_setup_conn_digest(csk)) { + csk->submode = 0; + return -1; + } + + return 0; +} + +static int cxgbit_set_iso_npdu(struct cxgbit_sock *csk) +{ + struct iscsi_conn *conn = csk->conn; + struct iscsi_conn_ops *conn_ops = conn->conn_ops; + struct iscsi_param *param; + u32 mrdsl, mbl; + u32 max_npdu, max_iso_npdu; + + if (conn->login->leading_connection) { + param = iscsi_find_param_from_key(DATASEQUENCEINORDER, + conn->param_list); + if (!param) { + pr_err("param not found key %s\n", DATASEQUENCEINORDER); + return -1; + } + + if (strcmp(param->value, YES)) + return 0; + + param = iscsi_find_param_from_key(DATAPDUINORDER, + conn->param_list); + if (!param) { + pr_err("param not found key %s\n", DATAPDUINORDER); + return -1; + } + + if (strcmp(param->value, YES)) + return 0; + + param = iscsi_find_param_from_key(MAXBURSTLENGTH, + conn->param_list); + if (!param) { + pr_err("param not found key %s\n", MAXBURSTLENGTH); + return -1; + } + + if (kstrtou32(param->value, 0, &mbl) < 0) + return -1; + } else { + if (!conn->sess->sess_ops->DataSequenceInOrder) + return 0; + if (!conn->sess->sess_ops->DataPDUInOrder) + return 0; + + mbl = conn->sess->sess_ops->MaxBurstLength; + } + + mrdsl = conn_ops->MaxRecvDataSegmentLength; + max_npdu = mbl / mrdsl; + + max_iso_npdu = CXGBIT_MAX_ISO_PAYLOAD / + (ISCSI_HDR_LEN + mrdsl + + cxgbit_digest_len[csk->submode]); + + csk->max_iso_npdu = min(max_npdu, max_iso_npdu); + + if (csk->max_iso_npdu <= 1) + csk->max_iso_npdu = 0; + + return 0; +} + +static int cxgbit_set_params(struct iscsi_conn *conn) +{ + struct cxgbit_sock *csk = conn->context; + struct cxgbit_device *cdev = csk->com.cdev; + struct cxgbi_ppm *ppm = *csk->com.cdev->lldi.iscsi_ppm; + struct iscsi_conn_ops *conn_ops = conn->conn_ops; + struct iscsi_param *param; + u8 erl; + + if (conn_ops->MaxRecvDataSegmentLength > cdev->mdsl) + conn_ops->MaxRecvDataSegmentLength = cdev->mdsl; + + if (conn->login->leading_connection) { + param = iscsi_find_param_from_key(ERRORRECOVERYLEVEL, + conn->param_list); + if (!param) { + pr_err("param not found key %s\n", ERRORRECOVERYLEVEL); + return -1; + } + if (kstrtou8(param->value, 0, &erl) < 0) + return -1; + } else { + erl = conn->sess->sess_ops->ErrorRecoveryLevel; + } + + if (!erl) { + if (test_bit(CDEV_ISO_ENABLE, &cdev->flags)) { + if (cxgbit_set_iso_npdu(csk)) + return -1; + } + + if (test_bit(CDEV_DDP_ENABLE, &cdev->flags)) { + if (cxgbit_setup_conn_pgidx(csk, + ppm->tformat.pgsz_idx_dflt)) + return -1; + set_bit(CSK_DDP_ENABLE, &csk->com.flags); + } + } + + if (cxgbit_set_digest(csk)) + return -1; + + return 0; +} + +int +cxgbit_put_login_tx(struct iscsi_conn *conn, struct iscsi_login *login, + u32 length) +{ + struct cxgbit_sock *csk = conn->context; + struct sk_buff *skb; + u32 padding_buf = 0; + u8 padding = ((-length) & 3); + + skb = cxgbit_alloc_skb(csk, length + padding); + if (!skb) + return -ENOMEM; + skb_store_bits(skb, 0, login->rsp, ISCSI_HDR_LEN); + skb_store_bits(skb, ISCSI_HDR_LEN, login->rsp_buf, length); + + if (padding) + skb_store_bits(skb, ISCSI_HDR_LEN + length, + &padding_buf, padding); + + if (login->login_complete) { + if (cxgbit_set_params(conn)) { + kfree_skb(skb); + return -1; + } + + set_bit(CSK_LOGIN_DONE, &csk->com.flags); + } + + if (cxgbit_queue_skb(csk, skb)) + return -1; + + if ((!login->login_complete) && (!login->login_failed)) + schedule_delayed_work(&conn->login_work, 0); + + return 0; +} + +static void +cxgbit_skb_copy_to_sg(struct sk_buff *skb, struct scatterlist *sg, + unsigned int nents) +{ + struct skb_seq_state st; + const u8 *buf; + unsigned int consumed = 0, buf_len; + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(skb); + + skb_prepare_seq_read(skb, pdu_cb->doffset, + pdu_cb->doffset + pdu_cb->dlen, + &st); + + while (true) { + buf_len = skb_seq_read(consumed, &buf, &st); + if (!buf_len) { + skb_abort_seq_read(&st); + break; + } + + consumed += sg_pcopy_from_buffer(sg, nents, (void *)buf, + buf_len, consumed); + } +} + +static struct iscsi_cmd *cxgbit_allocate_cmd(struct cxgbit_sock *csk) +{ + struct iscsi_conn *conn = csk->conn; + struct cxgbi_ppm *ppm = cdev2ppm(csk->com.cdev); + struct cxgbit_cmd *ccmd; + struct iscsi_cmd *cmd; + + cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE); + if (!cmd) { + pr_err("Unable to allocate iscsi_cmd + cxgbit_cmd\n"); + return NULL; + } + + ccmd = iscsit_priv_cmd(cmd); + ccmd->ttinfo.tag = ppm->tformat.no_ddp_mask; + ccmd->setup_ddp = true; + + return cmd; +} + +static int +cxgbit_handle_immediate_data(struct iscsi_cmd *cmd, struct iscsi_scsi_req *hdr, + u32 length) +{ + struct iscsi_conn *conn = cmd->conn; + struct cxgbit_sock *csk = conn->context; + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + + if (pdu_cb->flags & PDUCBF_RX_DCRC_ERR) { + pr_err("ImmediateData CRC32C DataDigest error\n"); + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Unable to recover from" + " Immediate Data digest failure while" + " in ERL=0.\n"); + iscsit_reject_cmd(cmd, ISCSI_REASON_DATA_DIGEST_ERROR, + (unsigned char *)hdr); + return IMMEDIATE_DATA_CANNOT_RECOVER; + } + + iscsit_reject_cmd(cmd, ISCSI_REASON_DATA_DIGEST_ERROR, + (unsigned char *)hdr); + return IMMEDIATE_DATA_ERL1_CRC_FAILURE; + } + + if (cmd->se_cmd.se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC) { + struct cxgbit_cmd *ccmd = iscsit_priv_cmd(cmd); + struct skb_shared_info *ssi = skb_shinfo(csk->skb); + skb_frag_t *dfrag = &ssi->frags[pdu_cb->dfrag_idx]; + + sg_init_table(&ccmd->sg, 1); + sg_set_page(&ccmd->sg, dfrag->page.p, skb_frag_size(dfrag), + dfrag->page_offset); + get_page(dfrag->page.p); + + cmd->se_cmd.t_data_sg = &ccmd->sg; + cmd->se_cmd.t_data_nents = 1; + + ccmd->release = true; + } else { + struct scatterlist *sg = &cmd->se_cmd.t_data_sg[0]; + u32 sg_nents = max(1UL, DIV_ROUND_UP(pdu_cb->dlen, PAGE_SIZE)); + + cxgbit_skb_copy_to_sg(csk->skb, sg, sg_nents); + } + + cmd->write_data_done += pdu_cb->dlen; + + if (cmd->write_data_done == cmd->se_cmd.data_length) { + spin_lock_bh(&cmd->istate_lock); + cmd->cmd_flags |= ICF_GOT_LAST_DATAOUT; + cmd->i_state = ISTATE_RECEIVED_LAST_DATAOUT; + spin_unlock_bh(&cmd->istate_lock); + } + + return IMMEDIATE_DATA_NORMAL_OPERATION; +} + +static int +cxgbit_get_immediate_data(struct iscsi_cmd *cmd, struct iscsi_scsi_req *hdr, + bool dump_payload) +{ + struct iscsi_conn *conn = cmd->conn; + int cmdsn_ret = 0, immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; + /* + * Special case for Unsupported SAM WRITE Opcodes and ImmediateData=Yes. + */ + if (dump_payload) + goto after_immediate_data; + + immed_ret = cxgbit_handle_immediate_data(cmd, hdr, + cmd->first_burst_len); +after_immediate_data: + if (immed_ret == IMMEDIATE_DATA_NORMAL_OPERATION) { + /* + * A PDU/CmdSN carrying Immediate Data passed + * DataCRC, check against ExpCmdSN/MaxCmdSN if + * Immediate Bit is not set. + */ + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, + (unsigned char *)hdr, + hdr->cmdsn); + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return -1; + + if (cmd->sense_reason || cmdsn_ret == CMDSN_LOWER_THAN_EXP) { + target_put_sess_cmd(&cmd->se_cmd); + return 0; + } else if (cmd->unsolicited_data) { + iscsit_set_unsoliticed_dataout(cmd); + } + + } else if (immed_ret == IMMEDIATE_DATA_ERL1_CRC_FAILURE) { + /* + * Immediate Data failed DataCRC and ERL>=1, + * silently drop this PDU and let the initiator + * plug the CmdSN gap. + * + * FIXME: Send Unsolicited NOPIN with reserved + * TTT here to help the initiator figure out + * the missing CmdSN, although they should be + * intelligent enough to determine the missing + * CmdSN and issue a retry to plug the sequence. + */ + cmd->i_state = ISTATE_REMOVE; + iscsit_add_cmd_to_immediate_queue(cmd, conn, cmd->i_state); + } else /* immed_ret == IMMEDIATE_DATA_CANNOT_RECOVER */ + return -1; + + return 0; +} + +static int +cxgbit_handle_scsi_cmd(struct cxgbit_sock *csk, struct iscsi_cmd *cmd) +{ + struct iscsi_conn *conn = csk->conn; + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + struct iscsi_scsi_req *hdr = (struct iscsi_scsi_req *)pdu_cb->hdr; + int rc; + bool dump_payload = false; + + rc = iscsit_setup_scsi_cmd(conn, cmd, (unsigned char *)hdr); + if (rc < 0) + return rc; + + if (pdu_cb->dlen && (pdu_cb->dlen == cmd->se_cmd.data_length) && + (pdu_cb->nr_dfrags == 1)) + cmd->se_cmd.se_cmd_flags |= SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC; + + rc = iscsit_process_scsi_cmd(conn, cmd, hdr); + if (rc < 0) + return 0; + else if (rc > 0) + dump_payload = true; + + if (!pdu_cb->dlen) + return 0; + + return cxgbit_get_immediate_data(cmd, hdr, dump_payload); +} + +static int cxgbit_handle_iscsi_dataout(struct cxgbit_sock *csk) +{ + struct scatterlist *sg_start; + struct iscsi_conn *conn = csk->conn; + struct iscsi_cmd *cmd = NULL; + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + struct iscsi_data *hdr = (struct iscsi_data *)pdu_cb->hdr; + u32 data_offset = be32_to_cpu(hdr->offset); + u32 data_len = pdu_cb->dlen; + int rc, sg_nents, sg_off; + bool dcrc_err = false; + + rc = iscsit_check_dataout_hdr(conn, (unsigned char *)hdr, &cmd); + if (rc < 0) + return rc; + else if (!cmd) + return 0; + + if (pdu_cb->flags & PDUCBF_RX_DCRC_ERR) { + pr_err("ITT: 0x%08x, Offset: %u, Length: %u," + " DataSN: 0x%08x\n", + hdr->itt, hdr->offset, data_len, + hdr->datasn); + + dcrc_err = true; + goto check_payload; + } + + pr_debug("DataOut data_len: %u, " + "write_data_done: %u, data_length: %u\n", + data_len, cmd->write_data_done, + cmd->se_cmd.data_length); + + if (!(pdu_cb->flags & PDUCBF_RX_DATA_DDPD)) { + sg_off = data_offset / PAGE_SIZE; + sg_start = &cmd->se_cmd.t_data_sg[sg_off]; + sg_nents = max(1UL, DIV_ROUND_UP(data_len, PAGE_SIZE)); + + cxgbit_skb_copy_to_sg(csk->skb, sg_start, sg_nents); + } + +check_payload: + + rc = iscsit_check_dataout_payload(cmd, hdr, dcrc_err); + if (rc < 0) + return rc; + + return 0; +} + +static int cxgbit_handle_nop_out(struct cxgbit_sock *csk, struct iscsi_cmd *cmd) +{ + struct iscsi_conn *conn = csk->conn; + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + struct iscsi_nopout *hdr = (struct iscsi_nopout *)pdu_cb->hdr; + unsigned char *ping_data = NULL; + u32 payload_length = pdu_cb->dlen; + int ret; + + ret = iscsit_setup_nop_out(conn, cmd, hdr); + if (ret < 0) + return 0; + + if (pdu_cb->flags & PDUCBF_RX_DCRC_ERR) { + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Unable to recover from" + " NOPOUT Ping DataCRC failure while in" + " ERL=0.\n"); + ret = -1; + goto out; + } else { + /* + * drop this PDU and let the + * initiator plug the CmdSN gap. + */ + pr_info("Dropping NOPOUT" + " Command CmdSN: 0x%08x due to" + " DataCRC error.\n", hdr->cmdsn); + ret = 0; + goto out; + } + } + + /* + * Handle NOP-OUT payload for traditional iSCSI sockets + */ + if (payload_length && hdr->ttt == cpu_to_be32(0xFFFFFFFF)) { + ping_data = kzalloc(payload_length + 1, GFP_KERNEL); + if (!ping_data) { + pr_err("Unable to allocate memory for" + " NOPOUT ping data.\n"); + ret = -1; + goto out; + } + + skb_copy_bits(csk->skb, pdu_cb->doffset, + ping_data, payload_length); + + ping_data[payload_length] = '\0'; + /* + * Attach ping data to struct iscsi_cmd->buf_ptr. + */ + cmd->buf_ptr = ping_data; + cmd->buf_ptr_size = payload_length; + + pr_debug("Got %u bytes of NOPOUT ping" + " data.\n", payload_length); + pr_debug("Ping Data: \"%s\"\n", ping_data); + } + + return iscsit_process_nop_out(conn, cmd, hdr); +out: + if (cmd) + iscsit_free_cmd(cmd, false); + return ret; +} + +static int +cxgbit_handle_text_cmd(struct cxgbit_sock *csk, struct iscsi_cmd *cmd) +{ + struct iscsi_conn *conn = csk->conn; + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + struct iscsi_text *hdr = (struct iscsi_text *)pdu_cb->hdr; + u32 payload_length = pdu_cb->dlen; + int rc; + unsigned char *text_in = NULL; + + rc = iscsit_setup_text_cmd(conn, cmd, hdr); + if (rc < 0) + return rc; + + if (pdu_cb->flags & PDUCBF_RX_DCRC_ERR) { + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Unable to recover from" + " Text Data digest failure while in" + " ERL=0.\n"); + goto reject; + } else { + /* + * drop this PDU and let the + * initiator plug the CmdSN gap. + */ + pr_info("Dropping Text" + " Command CmdSN: 0x%08x due to" + " DataCRC error.\n", hdr->cmdsn); + return 0; + } + } + + if (payload_length) { + text_in = kzalloc(payload_length, GFP_KERNEL); + if (!text_in) { + pr_err("Unable to allocate text_in of payload_length: %u\n", + payload_length); + return -ENOMEM; + } + skb_copy_bits(csk->skb, pdu_cb->doffset, + text_in, payload_length); + + text_in[payload_length - 1] = '\0'; + + cmd->text_in_ptr = text_in; + } + + return iscsit_process_text_cmd(conn, cmd, hdr); + +reject: + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, + pdu_cb->hdr); +} + +static int cxgbit_target_rx_opcode(struct cxgbit_sock *csk) +{ + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + struct iscsi_hdr *hdr = (struct iscsi_hdr *)pdu_cb->hdr; + struct iscsi_conn *conn = csk->conn; + struct iscsi_cmd *cmd = NULL; + u8 opcode = (hdr->opcode & ISCSI_OPCODE_MASK); + int ret = -EINVAL; + + switch (opcode) { + case ISCSI_OP_SCSI_CMD: + cmd = cxgbit_allocate_cmd(csk); + if (!cmd) + goto reject; + + ret = cxgbit_handle_scsi_cmd(csk, cmd); + break; + case ISCSI_OP_SCSI_DATA_OUT: + ret = cxgbit_handle_iscsi_dataout(csk); + break; + case ISCSI_OP_NOOP_OUT: + if (hdr->ttt == cpu_to_be32(0xFFFFFFFF)) { + cmd = cxgbit_allocate_cmd(csk); + if (!cmd) + goto reject; + } + + ret = cxgbit_handle_nop_out(csk, cmd); + break; + case ISCSI_OP_SCSI_TMFUNC: + cmd = cxgbit_allocate_cmd(csk); + if (!cmd) + goto reject; + + ret = iscsit_handle_task_mgt_cmd(conn, cmd, + (unsigned char *)hdr); + break; + case ISCSI_OP_TEXT: + if (hdr->ttt != cpu_to_be32(0xFFFFFFFF)) { + cmd = iscsit_find_cmd_from_itt(conn, hdr->itt); + if (!cmd) + goto reject; + } else { + cmd = cxgbit_allocate_cmd(csk); + if (!cmd) + goto reject; + } + + ret = cxgbit_handle_text_cmd(csk, cmd); + break; + case ISCSI_OP_LOGOUT: + cmd = cxgbit_allocate_cmd(csk); + if (!cmd) + goto reject; + + ret = iscsit_handle_logout_cmd(conn, cmd, (unsigned char *)hdr); + if (ret > 0) + wait_for_completion_timeout(&conn->conn_logout_comp, + SECONDS_FOR_LOGOUT_COMP + * HZ); + break; + case ISCSI_OP_SNACK: + ret = iscsit_handle_snack(conn, (unsigned char *)hdr); + break; + default: + pr_err("Got unknown iSCSI OpCode: 0x%02x\n", opcode); + dump_stack(); + break; + } + + return ret; + +reject: + return iscsit_add_reject(conn, ISCSI_REASON_BOOKMARK_NO_RESOURCES, + (unsigned char *)hdr); + return ret; +} + +static int cxgbit_rx_opcode(struct cxgbit_sock *csk) +{ + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + struct iscsi_conn *conn = csk->conn; + struct iscsi_hdr *hdr = pdu_cb->hdr; + u8 opcode; + + if (pdu_cb->flags & PDUCBF_RX_HCRC_ERR) { + atomic_long_inc(&conn->sess->conn_digest_errors); + goto transport_err; + } + + if (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT) + goto transport_err; + + opcode = hdr->opcode & ISCSI_OPCODE_MASK; + + if (conn->sess->sess_ops->SessionType && + ((!(opcode & ISCSI_OP_TEXT)) || + (!(opcode & ISCSI_OP_LOGOUT)))) { + pr_err("Received illegal iSCSI Opcode: 0x%02x" + " while in Discovery Session, rejecting.\n", opcode); + iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + goto transport_err; + } + + if (cxgbit_target_rx_opcode(csk) < 0) + goto transport_err; + + return 0; + +transport_err: + return -1; +} + +static int cxgbit_rx_login_pdu(struct cxgbit_sock *csk) +{ + struct iscsi_conn *conn = csk->conn; + struct iscsi_login *login = conn->login; + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_rx_pdu_cb(csk->skb); + struct iscsi_login_req *login_req; + + login_req = (struct iscsi_login_req *)login->req; + memcpy(login_req, pdu_cb->hdr, sizeof(*login_req)); + + pr_debug("Got Login Command, Flags 0x%02x, ITT: 0x%08x," + " CmdSN: 0x%08x, ExpStatSN: 0x%08x, CID: %hu, Length: %u\n", + login_req->flags, login_req->itt, login_req->cmdsn, + login_req->exp_statsn, login_req->cid, pdu_cb->dlen); + /* + * Setup the initial iscsi_login values from the leading + * login request PDU. + */ + if (login->first_request) { + login_req = (struct iscsi_login_req *)login->req; + login->leading_connection = (!login_req->tsih) ? 1 : 0; + login->current_stage = ISCSI_LOGIN_CURRENT_STAGE( + login_req->flags); + login->version_min = login_req->min_version; + login->version_max = login_req->max_version; + memcpy(login->isid, login_req->isid, 6); + login->cmd_sn = be32_to_cpu(login_req->cmdsn); + login->init_task_tag = login_req->itt; + login->initial_exp_statsn = be32_to_cpu(login_req->exp_statsn); + login->cid = be16_to_cpu(login_req->cid); + login->tsih = be16_to_cpu(login_req->tsih); + } + + if (iscsi_target_check_login_request(conn, login) < 0) + return -1; + + memset(login->req_buf, 0, MAX_KEY_VALUE_PAIRS); + skb_copy_bits(csk->skb, pdu_cb->doffset, login->req_buf, pdu_cb->dlen); + + return 0; +} + +static int +cxgbit_process_iscsi_pdu(struct cxgbit_sock *csk, struct sk_buff *skb, int idx) +{ + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb, idx); + int ret; + + cxgbit_rx_pdu_cb(skb) = pdu_cb; + + csk->skb = skb; + + if (!test_bit(CSK_LOGIN_DONE, &csk->com.flags)) { + ret = cxgbit_rx_login_pdu(csk); + set_bit(CSK_LOGIN_PDU_DONE, &csk->com.flags); + } else { + ret = cxgbit_rx_opcode(csk); + } + + return ret; +} + +static void cxgbit_lro_skb_dump(struct sk_buff *skb) +{ + struct skb_shared_info *ssi = skb_shinfo(skb); + struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb); + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb, 0); + u8 i; + + pr_info("skb 0x%p, head 0x%p, 0x%p, len %u,%u, frags %u.\n", + skb, skb->head, skb->data, skb->len, skb->data_len, + ssi->nr_frags); + pr_info("skb 0x%p, lro_cb, csk 0x%p, pdu %u, %u.\n", + skb, lro_cb->csk, lro_cb->pdu_idx, lro_cb->pdu_totallen); + + for (i = 0; i < lro_cb->pdu_idx; i++, pdu_cb++) + pr_info("skb 0x%p, pdu %d, %u, f 0x%x, seq 0x%x, dcrc 0x%x, " + "frags %u.\n", + skb, i, pdu_cb->pdulen, pdu_cb->flags, pdu_cb->seq, + pdu_cb->ddigest, pdu_cb->frags); + for (i = 0; i < ssi->nr_frags; i++) + pr_info("skb 0x%p, frag %d, off %u, sz %u.\n", + skb, i, ssi->frags[i].page_offset, ssi->frags[i].size); +} + +static void cxgbit_lro_hskb_reset(struct cxgbit_sock *csk) +{ + struct sk_buff *skb = csk->lro_hskb; + struct skb_shared_info *ssi = skb_shinfo(skb); + u8 i; + + memset(skb->data, 0, LRO_SKB_MIN_HEADROOM); + for (i = 0; i < ssi->nr_frags; i++) + put_page(skb_frag_page(&ssi->frags[i])); + ssi->nr_frags = 0; +} + +static void +cxgbit_lro_skb_merge(struct cxgbit_sock *csk, struct sk_buff *skb, u8 pdu_idx) +{ + struct sk_buff *hskb = csk->lro_hskb; + struct cxgbit_lro_pdu_cb *hpdu_cb = cxgbit_skb_lro_pdu_cb(hskb, 0); + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb, pdu_idx); + struct skb_shared_info *hssi = skb_shinfo(hskb); + struct skb_shared_info *ssi = skb_shinfo(skb); + unsigned int len = 0; + + if (pdu_cb->flags & PDUCBF_RX_HDR) { + hpdu_cb->flags = pdu_cb->flags; + hpdu_cb->seq = pdu_cb->seq; + hpdu_cb->hdr = pdu_cb->hdr; + hpdu_cb->hlen = pdu_cb->hlen; + + memcpy(&hssi->frags[0], &ssi->frags[pdu_cb->hfrag_idx], + sizeof(skb_frag_t)); + + get_page(skb_frag_page(&hssi->frags[0])); + hssi->nr_frags = 1; + hpdu_cb->frags = 1; + hpdu_cb->hfrag_idx = 0; + + len = hssi->frags[0].size; + hskb->len = len; + hskb->data_len = len; + hskb->truesize = len; + } + + if (pdu_cb->flags & PDUCBF_RX_DATA) { + u8 hfrag_idx = 1, i; + + hpdu_cb->flags |= pdu_cb->flags; + + len = 0; + for (i = 0; i < pdu_cb->nr_dfrags; hfrag_idx++, i++) { + memcpy(&hssi->frags[hfrag_idx], + &ssi->frags[pdu_cb->dfrag_idx + i], + sizeof(skb_frag_t)); + + get_page(skb_frag_page(&hssi->frags[hfrag_idx])); + + len += hssi->frags[hfrag_idx].size; + + hssi->nr_frags++; + hpdu_cb->frags++; + } + + hpdu_cb->dlen = pdu_cb->dlen; + hpdu_cb->doffset = hpdu_cb->hlen; + hpdu_cb->nr_dfrags = pdu_cb->nr_dfrags; + hpdu_cb->dfrag_idx = 1; + hskb->len += len; + hskb->data_len += len; + hskb->truesize += len; + } + + if (pdu_cb->flags & PDUCBF_RX_STATUS) { + hpdu_cb->flags |= pdu_cb->flags; + + if (hpdu_cb->flags & PDUCBF_RX_DATA) + hpdu_cb->flags &= ~PDUCBF_RX_DATA_DDPD; + + hpdu_cb->ddigest = pdu_cb->ddigest; + hpdu_cb->pdulen = pdu_cb->pdulen; + } +} + +static int cxgbit_process_lro_skb(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb); + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb, 0); + u8 pdu_idx = 0, last_idx = 0; + int ret = 0; + + if (!pdu_cb->complete) { + cxgbit_lro_skb_merge(csk, skb, 0); + + if (pdu_cb->flags & PDUCBF_RX_STATUS) { + struct sk_buff *hskb = csk->lro_hskb; + + ret = cxgbit_process_iscsi_pdu(csk, hskb, 0); + + cxgbit_lro_hskb_reset(csk); + + if (ret < 0) + goto out; + } + + pdu_idx = 1; + } + + if (lro_cb->pdu_idx) + last_idx = lro_cb->pdu_idx - 1; + + for (; pdu_idx <= last_idx; pdu_idx++) { + ret = cxgbit_process_iscsi_pdu(csk, skb, pdu_idx); + if (ret < 0) + goto out; + } + + if ((!lro_cb->complete) && lro_cb->pdu_idx) + cxgbit_lro_skb_merge(csk, skb, lro_cb->pdu_idx); + +out: + return ret; +} + +static int cxgbit_rx_lro_skb(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb); + struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb, 0); + int ret = -1; + + if ((pdu_cb->flags & PDUCBF_RX_HDR) && + (pdu_cb->seq != csk->rcv_nxt)) { + pr_info("csk 0x%p, tid 0x%x, seq 0x%x != 0x%x.\n", + csk, csk->tid, pdu_cb->seq, csk->rcv_nxt); + cxgbit_lro_skb_dump(skb); + return ret; + } + + csk->rcv_nxt += lro_cb->pdu_totallen; + + ret = cxgbit_process_lro_skb(csk, skb); + + csk->rx_credits += lro_cb->pdu_totallen; + + if (csk->rx_credits >= (csk->rcv_win / 4)) + cxgbit_rx_data_ack(csk); + + return ret; +} + +static int cxgbit_rx_skb(struct cxgbit_sock *csk, struct sk_buff *skb) +{ + int ret = -1; + + if (likely(cxgbit_skcb_flags(skb) & SKCBF_RX_LRO)) + ret = cxgbit_rx_lro_skb(csk, skb); + + __kfree_skb(skb); + return ret; +} + +static bool cxgbit_rxq_len(struct cxgbit_sock *csk, struct sk_buff_head *rxq) +{ + spin_lock_bh(&csk->rxq.lock); + if (skb_queue_len(&csk->rxq)) { + skb_queue_splice_init(&csk->rxq, rxq); + spin_unlock_bh(&csk->rxq.lock); + return true; + } + spin_unlock_bh(&csk->rxq.lock); + return false; +} + +static int cxgbit_wait_rxq(struct cxgbit_sock *csk) +{ + struct sk_buff *skb; + struct sk_buff_head rxq; + + skb_queue_head_init(&rxq); + + wait_event_interruptible(csk->waitq, cxgbit_rxq_len(csk, &rxq)); + + if (signal_pending(current)) + goto out; + + while ((skb = __skb_dequeue(&rxq))) { + if (cxgbit_rx_skb(csk, skb)) + goto out; + } + + return 0; +out: + __skb_queue_purge(&rxq); + return -1; +} + +int cxgbit_get_login_rx(struct iscsi_conn *conn, struct iscsi_login *login) +{ + struct cxgbit_sock *csk = conn->context; + int ret = -1; + + while (!test_and_clear_bit(CSK_LOGIN_PDU_DONE, &csk->com.flags)) { + ret = cxgbit_wait_rxq(csk); + if (ret) { + clear_bit(CSK_LOGIN_PDU_DONE, &csk->com.flags); + break; + } + } + + return ret; +} + +void cxgbit_get_rx_pdu(struct iscsi_conn *conn) +{ + struct cxgbit_sock *csk = conn->context; + + while (!kthread_should_stop()) { + iscsit_thread_check_cpumask(conn, current, 0); + if (cxgbit_wait_rxq(csk)) + return; + } +} From bc6e6bb470eda42f44bcac96c261cff1216577b3 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 May 2016 15:45:19 +0200 Subject: [PATCH 099/393] target: consolidate and fix session shutdown Factor out a helper to shutdown sessions for a Node ACL, and make it properly restart the list walk after dropping the lock. Signed-off-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- drivers/target/target_core_tpg.c | 91 ++++++++++++-------------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/drivers/target/target_core_tpg.c b/drivers/target/target_core_tpg.c index ddf046080dc3..88db4938600b 100644 --- a/drivers/target/target_core_tpg.c +++ b/drivers/target/target_core_tpg.c @@ -336,44 +336,47 @@ struct se_node_acl *core_tpg_add_initiator_node_acl( return acl; } +static void target_shutdown_sessions(struct se_node_acl *acl, bool acl_stop) +{ + struct se_session *sess; + unsigned long flags; + int ret; + + spin_lock_irqsave(&acl->nacl_sess_lock, flags); + if (acl_stop) + acl->acl_stop = 1; +restart: + list_for_each_entry(sess, &acl->acl_sess_list, sess_acl_list) { + if (sess->sess_tearing_down) + continue; + if (!target_get_session(sess)) + continue; + + list_del(&sess->sess_acl_list); + + spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); + ret = acl->se_tpg->se_tpg_tfo->shutdown_session(sess); + target_put_session(sess); + if (ret) + target_put_session(sess); + spin_lock_irqsave(&acl->nacl_sess_lock, flags); + goto restart; + } + spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); +} + void core_tpg_del_initiator_node_acl(struct se_node_acl *acl) { struct se_portal_group *tpg = acl->se_tpg; - LIST_HEAD(sess_list); - struct se_session *sess, *sess_tmp; - unsigned long flags; - int rc; mutex_lock(&tpg->acl_node_mutex); - if (acl->dynamic_node_acl) { + if (acl->dynamic_node_acl) acl->dynamic_node_acl = 0; - } list_del(&acl->acl_list); mutex_unlock(&tpg->acl_node_mutex); - spin_lock_irqsave(&acl->nacl_sess_lock, flags); - acl->acl_stop = 1; + target_shutdown_sessions(acl, true); - list_for_each_entry_safe(sess, sess_tmp, &acl->acl_sess_list, - sess_acl_list) { - if (sess->sess_tearing_down != 0) - continue; - - if (!target_get_session(sess)) - continue; - list_move(&sess->sess_acl_list, &sess_list); - } - spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); - - list_for_each_entry_safe(sess, sess_tmp, &sess_list, sess_acl_list) { - list_del(&sess->sess_acl_list); - - rc = tpg->se_tpg_tfo->shutdown_session(sess); - target_put_session(sess); - if (!rc) - continue; - target_put_session(sess); - } target_put_nacl(acl); /* * Wait for last target_put_nacl() to complete in target_complete_nacl() @@ -400,11 +403,7 @@ int core_tpg_set_initiator_node_queue_depth( struct se_node_acl *acl, u32 queue_depth) { - LIST_HEAD(sess_list); struct se_portal_group *tpg = acl->se_tpg; - struct se_session *sess, *sess_tmp; - unsigned long flags; - int rc; /* * User has requested to change the queue depth for a Initiator Node. @@ -413,30 +412,10 @@ int core_tpg_set_initiator_node_queue_depth( */ target_set_nacl_queue_depth(tpg, acl, queue_depth); - spin_lock_irqsave(&acl->nacl_sess_lock, flags); - list_for_each_entry_safe(sess, sess_tmp, &acl->acl_sess_list, - sess_acl_list) { - if (sess->sess_tearing_down != 0) - continue; - if (!target_get_session(sess)) - continue; - spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); - - /* - * Finally call tpg->se_tpg_tfo->close_session() to force session - * reinstatement to occur if there is an active session for the - * $FABRIC_MOD Initiator Node in question. - */ - rc = tpg->se_tpg_tfo->shutdown_session(sess); - target_put_session(sess); - if (!rc) { - spin_lock_irqsave(&acl->nacl_sess_lock, flags); - continue; - } - target_put_session(sess); - spin_lock_irqsave(&acl->nacl_sess_lock, flags); - } - spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); + /* + * Shutdown all pending sessions to force session reinstatement. + */ + target_shutdown_sessions(acl, false); pr_debug("Successfully changed queue depth to: %d for Initiator" " Node: %s on %s Target Portal Group: %u\n", acl->queue_depth, From fba81f8831b20272a97a990e5d47c332e9b1f65d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 May 2016 15:45:20 +0200 Subject: [PATCH 100/393] target: remove acl_stop Ensure we can use list_empty on the sess_acl_list to remove the need for this flag. Signed-off-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- drivers/target/target_core_tpg.c | 13 +++++-------- drivers/target/target_core_transport.c | 4 ++-- include/target/target_core_base.h | 1 - 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/target/target_core_tpg.c b/drivers/target/target_core_tpg.c index 88db4938600b..b1ac600f5b45 100644 --- a/drivers/target/target_core_tpg.c +++ b/drivers/target/target_core_tpg.c @@ -336,30 +336,27 @@ struct se_node_acl *core_tpg_add_initiator_node_acl( return acl; } -static void target_shutdown_sessions(struct se_node_acl *acl, bool acl_stop) +static void target_shutdown_sessions(struct se_node_acl *acl) { struct se_session *sess; unsigned long flags; int ret; - spin_lock_irqsave(&acl->nacl_sess_lock, flags); - if (acl_stop) - acl->acl_stop = 1; restart: + spin_lock_irqsave(&acl->nacl_sess_lock, flags); list_for_each_entry(sess, &acl->acl_sess_list, sess_acl_list) { if (sess->sess_tearing_down) continue; if (!target_get_session(sess)) continue; - list_del(&sess->sess_acl_list); + list_del_init(&sess->sess_acl_list); spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); ret = acl->se_tpg->se_tpg_tfo->shutdown_session(sess); target_put_session(sess); if (ret) target_put_session(sess); - spin_lock_irqsave(&acl->nacl_sess_lock, flags); goto restart; } spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); @@ -375,7 +372,7 @@ void core_tpg_del_initiator_node_acl(struct se_node_acl *acl) list_del(&acl->acl_list); mutex_unlock(&tpg->acl_node_mutex); - target_shutdown_sessions(acl, true); + target_shutdown_sessions(acl); target_put_nacl(acl); /* @@ -415,7 +412,7 @@ int core_tpg_set_initiator_node_queue_depth( /* * Shutdown all pending sessions to force session reinstatement. */ - target_shutdown_sessions(acl, false); + target_shutdown_sessions(acl); pr_debug("Successfully changed queue depth to: %d for Initiator" " Node: %s on %s Target Portal Group: %u\n", acl->queue_depth, diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index ab2bf12975e1..ccd3a0eda0c8 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -499,8 +499,8 @@ void transport_deregister_session_configfs(struct se_session *se_sess) se_nacl = se_sess->se_node_acl; if (se_nacl) { spin_lock_irqsave(&se_nacl->nacl_sess_lock, flags); - if (se_nacl->acl_stop == 0) - list_del(&se_sess->sess_acl_list); + if (!list_empty(&se_sess->sess_acl_list)) + list_del_init(&se_sess->sess_acl_list); /* * If the session list is empty, then clear the pointer. * Otherwise, set the struct se_session pointer from the tail diff --git a/include/target/target_core_base.h b/include/target/target_core_base.h index 3e0dd86360a2..1d4e1cb7f373 100644 --- a/include/target/target_core_base.h +++ b/include/target/target_core_base.h @@ -536,7 +536,6 @@ struct se_node_acl { char initiatorname[TRANSPORT_IQN_LEN]; /* Used to signal demo mode created ACL, disabled by default */ bool dynamic_node_acl; - bool acl_stop:1; u32 queue_depth; u32 acl_index; enum target_prot_type saved_prot_type; From 22d11759a4e7018f8cd7914e4e706ca2c96d6c01 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 May 2016 15:45:21 +0200 Subject: [PATCH 101/393] target: make ->shutdown_session optional Turns out the template and thus many drivers got the return value wrong: 0 means the fabrics driver needs to put a session reference, which no driver except for the iSCSI target drivers did. Fortunately none of these drivers supports explicit Node ACLs, so the bug was harmless. Even without that only qla2xxx and iscsi every did real work in shutdown_session, so get rid of the boilerplate code in all other drivers. Signed-off-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- Documentation/target/tcm_mod_builder.py | 8 -------- drivers/infiniband/ulp/srpt/ib_srpt.c | 9 --------- drivers/target/loopback/tcm_loop.c | 6 ------ drivers/target/sbp/sbp_target.c | 6 ------ drivers/target/target_core_configfs.c | 4 ---- drivers/target/target_core_tpg.c | 5 ++++- drivers/target/tcm_fc/tcm_fc.h | 1 - drivers/target/tcm_fc/tfc_conf.c | 1 - drivers/target/tcm_fc/tfc_sess.c | 12 ------------ drivers/usb/gadget/function/f_tcm.c | 6 ------ drivers/vhost/scsi.c | 6 ------ drivers/xen/xen-scsiback.c | 6 ------ 12 files changed, 4 insertions(+), 66 deletions(-) diff --git a/Documentation/target/tcm_mod_builder.py b/Documentation/target/tcm_mod_builder.py index 7d370c9b1450..6b8aa4086c5d 100755 --- a/Documentation/target/tcm_mod_builder.py +++ b/Documentation/target/tcm_mod_builder.py @@ -294,7 +294,6 @@ def tcm_mod_build_configfs(proto_ident, fabric_mod_dir_var, fabric_mod_name): buf += " .tpg_check_prod_mode_write_protect = " + fabric_mod_name + "_check_false,\n" buf += " .tpg_get_inst_index = " + fabric_mod_name + "_tpg_get_inst_index,\n" buf += " .release_cmd = " + fabric_mod_name + "_release_cmd,\n" - buf += " .shutdown_session = " + fabric_mod_name + "_shutdown_session,\n" buf += " .close_session = " + fabric_mod_name + "_close_session,\n" buf += " .sess_get_index = " + fabric_mod_name + "_sess_get_index,\n" buf += " .sess_get_initiator_sid = NULL,\n" @@ -467,13 +466,6 @@ def tcm_mod_dump_fabric_ops(proto_ident, fabric_mod_dir_var, fabric_mod_name): buf += "}\n\n" bufi += "void " + fabric_mod_name + "_release_cmd(struct se_cmd *);\n" - if re.search('shutdown_session\)\(', fo): - buf += "int " + fabric_mod_name + "_shutdown_session(struct se_session *se_sess)\n" - buf += "{\n" - buf += " return 0;\n" - buf += "}\n\n" - bufi += "int " + fabric_mod_name + "_shutdown_session(struct se_session *);\n" - if re.search('close_session\)\(', fo): buf += "void " + fabric_mod_name + "_close_session(struct se_session *se_sess)\n" buf += "{\n" diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.c b/drivers/infiniband/ulp/srpt/ib_srpt.c index 8b42401d4795..3b425af04572 100644 --- a/drivers/infiniband/ulp/srpt/ib_srpt.c +++ b/drivers/infiniband/ulp/srpt/ib_srpt.c @@ -1960,14 +1960,6 @@ static void __srpt_close_all_ch(struct srpt_device *sdev) } } -/** - * srpt_shutdown_session() - Whether or not a session may be shut down. - */ -static int srpt_shutdown_session(struct se_session *se_sess) -{ - return 1; -} - static void srpt_free_ch(struct kref *kref) { struct srpt_rdma_ch *ch = container_of(kref, struct srpt_rdma_ch, kref); @@ -3287,7 +3279,6 @@ static const struct target_core_fabric_ops srpt_template = { .tpg_get_inst_index = srpt_tpg_get_inst_index, .release_cmd = srpt_release_cmd, .check_stop_free = srpt_check_stop_free, - .shutdown_session = srpt_shutdown_session, .close_session = srpt_close_session, .sess_get_index = srpt_sess_get_index, .sess_get_initiator_sid = NULL, diff --git a/drivers/target/loopback/tcm_loop.c b/drivers/target/loopback/tcm_loop.c index 0ad5ac541a7f..0941b2c13f5a 100644 --- a/drivers/target/loopback/tcm_loop.c +++ b/drivers/target/loopback/tcm_loop.c @@ -601,11 +601,6 @@ static int tcm_loop_get_cmd_state(struct se_cmd *se_cmd) return tl_cmd->sc_cmd_state; } -static int tcm_loop_shutdown_session(struct se_session *se_sess) -{ - return 0; -} - static void tcm_loop_close_session(struct se_session *se_sess) { return; @@ -1243,7 +1238,6 @@ static const struct target_core_fabric_ops loop_ops = { .tpg_get_inst_index = tcm_loop_get_inst_index, .check_stop_free = tcm_loop_check_stop_free, .release_cmd = tcm_loop_release_cmd, - .shutdown_session = tcm_loop_shutdown_session, .close_session = tcm_loop_close_session, .sess_get_index = tcm_loop_sess_get_index, .write_pending = tcm_loop_write_pending, diff --git a/drivers/target/sbp/sbp_target.c b/drivers/target/sbp/sbp_target.c index c57e7884973d..6c8b8f84a047 100644 --- a/drivers/target/sbp/sbp_target.c +++ b/drivers/target/sbp/sbp_target.c @@ -1726,11 +1726,6 @@ static void sbp_release_cmd(struct se_cmd *se_cmd) sbp_free_request(req); } -static int sbp_shutdown_session(struct se_session *se_sess) -{ - return 0; -} - static void sbp_close_session(struct se_session *se_sess) { return; @@ -2349,7 +2344,6 @@ static const struct target_core_fabric_ops sbp_ops = { .tpg_check_prod_mode_write_protect = sbp_check_false, .tpg_get_inst_index = sbp_tpg_get_inst_index, .release_cmd = sbp_release_cmd, - .shutdown_session = sbp_shutdown_session, .close_session = sbp_close_session, .sess_get_index = sbp_sess_get_index, .write_pending = sbp_write_pending, diff --git a/drivers/target/target_core_configfs.c b/drivers/target/target_core_configfs.c index 73c23c75528a..75c5000ca95e 100644 --- a/drivers/target/target_core_configfs.c +++ b/drivers/target/target_core_configfs.c @@ -385,10 +385,6 @@ static int target_fabric_tf_ops_check(const struct target_core_fabric_ops *tfo) pr_err("Missing tfo->release_cmd()\n"); return -EINVAL; } - if (!tfo->shutdown_session) { - pr_err("Missing tfo->shutdown_session()\n"); - return -EINVAL; - } if (!tfo->close_session) { pr_err("Missing tfo->close_session()\n"); return -EINVAL; diff --git a/drivers/target/target_core_tpg.c b/drivers/target/target_core_tpg.c index b1ac600f5b45..a81a37045403 100644 --- a/drivers/target/target_core_tpg.c +++ b/drivers/target/target_core_tpg.c @@ -353,7 +353,10 @@ static void target_shutdown_sessions(struct se_node_acl *acl) list_del_init(&sess->sess_acl_list); spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); - ret = acl->se_tpg->se_tpg_tfo->shutdown_session(sess); + if (acl->se_tpg->se_tpg_tfo->shutdown_session) + ret = acl->se_tpg->se_tpg_tfo->shutdown_session(sess); + else + ret = 1; target_put_session(sess); if (ret) target_put_session(sess); diff --git a/drivers/target/tcm_fc/tcm_fc.h b/drivers/target/tcm_fc/tcm_fc.h index c30003bd4ff0..e28209b99b59 100644 --- a/drivers/target/tcm_fc/tcm_fc.h +++ b/drivers/target/tcm_fc/tcm_fc.h @@ -139,7 +139,6 @@ extern unsigned int ft_debug_logging; * Session ops. */ void ft_sess_put(struct ft_sess *); -int ft_sess_shutdown(struct se_session *); void ft_sess_close(struct se_session *); u32 ft_sess_get_index(struct se_session *); u32 ft_sess_get_port_name(struct se_session *, unsigned char *, u32); diff --git a/drivers/target/tcm_fc/tfc_conf.c b/drivers/target/tcm_fc/tfc_conf.c index 4d375e95841b..42ee91123dca 100644 --- a/drivers/target/tcm_fc/tfc_conf.c +++ b/drivers/target/tcm_fc/tfc_conf.c @@ -442,7 +442,6 @@ static const struct target_core_fabric_ops ft_fabric_ops = { .tpg_get_inst_index = ft_tpg_get_inst_index, .check_stop_free = ft_check_stop_free, .release_cmd = ft_release_cmd, - .shutdown_session = ft_sess_shutdown, .close_session = ft_sess_close, .sess_get_index = ft_sess_get_index, .sess_get_initiator_sid = NULL, diff --git a/drivers/target/tcm_fc/tfc_sess.c b/drivers/target/tcm_fc/tfc_sess.c index d0c3e1894c61..f5186a744399 100644 --- a/drivers/target/tcm_fc/tfc_sess.c +++ b/drivers/target/tcm_fc/tfc_sess.c @@ -302,18 +302,6 @@ static void ft_sess_delete_all(struct ft_tport *tport) * TCM ops for sessions. */ -/* - * Determine whether session is allowed to be shutdown in the current context. - * Returns non-zero if the session should be shutdown. - */ -int ft_sess_shutdown(struct se_session *se_sess) -{ - struct ft_sess *sess = se_sess->fabric_sess_ptr; - - pr_debug("port_id %x\n", sess->port_id); - return 1; -} - /* * Remove session and send PRLO. * This is called when the ACL is being deleted or queue depth is changing. diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index 2ace0295408e..f4ed427e04ce 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -1290,11 +1290,6 @@ static void usbg_release_cmd(struct se_cmd *se_cmd) percpu_ida_free(&se_sess->sess_tag_pool, se_cmd->map_tag); } -static int usbg_shutdown_session(struct se_session *se_sess) -{ - return 0; -} - static void usbg_close_session(struct se_session *se_sess) { } @@ -1735,7 +1730,6 @@ static const struct target_core_fabric_ops usbg_ops = { .tpg_check_prod_mode_write_protect = usbg_check_false, .tpg_get_inst_index = usbg_tpg_get_inst_index, .release_cmd = usbg_release_cmd, - .shutdown_session = usbg_shutdown_session, .close_session = usbg_close_session, .sess_get_index = usbg_sess_get_index, .sess_get_initiator_sid = NULL, diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 0e6fd556c982..d2c60e5c48b3 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -333,11 +333,6 @@ static void vhost_scsi_release_cmd(struct se_cmd *se_cmd) percpu_ida_free(&se_sess->sess_tag_pool, se_cmd->map_tag); } -static int vhost_scsi_shutdown_session(struct se_session *se_sess) -{ - return 0; -} - static void vhost_scsi_close_session(struct se_session *se_sess) { return; @@ -2114,7 +2109,6 @@ static struct target_core_fabric_ops vhost_scsi_ops = { .tpg_get_inst_index = vhost_scsi_tpg_get_inst_index, .release_cmd = vhost_scsi_release_cmd, .check_stop_free = vhost_scsi_check_stop_free, - .shutdown_session = vhost_scsi_shutdown_session, .close_session = vhost_scsi_close_session, .sess_get_index = vhost_scsi_sess_get_index, .sess_get_initiator_sid = NULL, diff --git a/drivers/xen/xen-scsiback.c b/drivers/xen/xen-scsiback.c index ff932624eaad..783947a272ab 100644 --- a/drivers/xen/xen-scsiback.c +++ b/drivers/xen/xen-scsiback.c @@ -1399,11 +1399,6 @@ static void scsiback_release_cmd(struct se_cmd *se_cmd) percpu_ida_free(&se_sess->sess_tag_pool, se_cmd->map_tag); } -static int scsiback_shutdown_session(struct se_session *se_sess) -{ - return 0; -} - static void scsiback_close_session(struct se_session *se_sess) { } @@ -1841,7 +1836,6 @@ static const struct target_core_fabric_ops scsiback_ops = { .tpg_get_inst_index = scsiback_tpg_get_inst_index, .check_stop_free = scsiback_check_stop_free, .release_cmd = scsiback_release_cmd, - .shutdown_session = scsiback_shutdown_session, .close_session = scsiback_close_session, .sess_get_index = scsiback_sess_get_index, .sess_get_initiator_sid = NULL, From 36ec2ddc0d9309d52e14eb84c0807a78604460dc Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 May 2016 15:45:22 +0200 Subject: [PATCH 102/393] target: make close_session optional Signed-off-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- Documentation/target/tcm_mod_builder.py | 8 -------- drivers/target/loopback/tcm_loop.c | 6 ------ drivers/target/sbp/sbp_target.c | 6 ------ drivers/target/target_core_configfs.c | 4 ---- drivers/target/target_core_transport.c | 3 ++- drivers/usb/gadget/function/f_tcm.c | 5 ----- drivers/vhost/scsi.c | 6 ------ drivers/xen/xen-scsiback.c | 5 ----- 8 files changed, 2 insertions(+), 41 deletions(-) diff --git a/Documentation/target/tcm_mod_builder.py b/Documentation/target/tcm_mod_builder.py index 6b8aa4086c5d..94bf6944bb1e 100755 --- a/Documentation/target/tcm_mod_builder.py +++ b/Documentation/target/tcm_mod_builder.py @@ -294,7 +294,6 @@ def tcm_mod_build_configfs(proto_ident, fabric_mod_dir_var, fabric_mod_name): buf += " .tpg_check_prod_mode_write_protect = " + fabric_mod_name + "_check_false,\n" buf += " .tpg_get_inst_index = " + fabric_mod_name + "_tpg_get_inst_index,\n" buf += " .release_cmd = " + fabric_mod_name + "_release_cmd,\n" - buf += " .close_session = " + fabric_mod_name + "_close_session,\n" buf += " .sess_get_index = " + fabric_mod_name + "_sess_get_index,\n" buf += " .sess_get_initiator_sid = NULL,\n" buf += " .write_pending = " + fabric_mod_name + "_write_pending,\n" @@ -466,13 +465,6 @@ def tcm_mod_dump_fabric_ops(proto_ident, fabric_mod_dir_var, fabric_mod_name): buf += "}\n\n" bufi += "void " + fabric_mod_name + "_release_cmd(struct se_cmd *);\n" - if re.search('close_session\)\(', fo): - buf += "void " + fabric_mod_name + "_close_session(struct se_session *se_sess)\n" - buf += "{\n" - buf += " return;\n" - buf += "}\n\n" - bufi += "void " + fabric_mod_name + "_close_session(struct se_session *);\n" - if re.search('sess_get_index\)\(', fo): buf += "u32 " + fabric_mod_name + "_sess_get_index(struct se_session *se_sess)\n" buf += "{\n" diff --git a/drivers/target/loopback/tcm_loop.c b/drivers/target/loopback/tcm_loop.c index 0941b2c13f5a..5091b31b3e56 100644 --- a/drivers/target/loopback/tcm_loop.c +++ b/drivers/target/loopback/tcm_loop.c @@ -601,11 +601,6 @@ static int tcm_loop_get_cmd_state(struct se_cmd *se_cmd) return tl_cmd->sc_cmd_state; } -static void tcm_loop_close_session(struct se_session *se_sess) -{ - return; -}; - static int tcm_loop_write_pending(struct se_cmd *se_cmd) { /* @@ -1238,7 +1233,6 @@ static const struct target_core_fabric_ops loop_ops = { .tpg_get_inst_index = tcm_loop_get_inst_index, .check_stop_free = tcm_loop_check_stop_free, .release_cmd = tcm_loop_release_cmd, - .close_session = tcm_loop_close_session, .sess_get_index = tcm_loop_sess_get_index, .write_pending = tcm_loop_write_pending, .write_pending_status = tcm_loop_write_pending_status, diff --git a/drivers/target/sbp/sbp_target.c b/drivers/target/sbp/sbp_target.c index 6c8b8f84a047..58bb6ed18185 100644 --- a/drivers/target/sbp/sbp_target.c +++ b/drivers/target/sbp/sbp_target.c @@ -1726,11 +1726,6 @@ static void sbp_release_cmd(struct se_cmd *se_cmd) sbp_free_request(req); } -static void sbp_close_session(struct se_session *se_sess) -{ - return; -} - static u32 sbp_sess_get_index(struct se_session *se_sess) { return 0; @@ -2344,7 +2339,6 @@ static const struct target_core_fabric_ops sbp_ops = { .tpg_check_prod_mode_write_protect = sbp_check_false, .tpg_get_inst_index = sbp_tpg_get_inst_index, .release_cmd = sbp_release_cmd, - .close_session = sbp_close_session, .sess_get_index = sbp_sess_get_index, .write_pending = sbp_write_pending, .write_pending_status = sbp_write_pending_status, diff --git a/drivers/target/target_core_configfs.c b/drivers/target/target_core_configfs.c index 75c5000ca95e..2001005bef45 100644 --- a/drivers/target/target_core_configfs.c +++ b/drivers/target/target_core_configfs.c @@ -385,10 +385,6 @@ static int target_fabric_tf_ops_check(const struct target_core_fabric_ops *tfo) pr_err("Missing tfo->release_cmd()\n"); return -EINVAL; } - if (!tfo->close_session) { - pr_err("Missing tfo->close_session()\n"); - return -EINVAL; - } if (!tfo->sess_get_index) { pr_err("Missing tfo->sess_get_index()\n"); return -EINVAL; diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index ccd3a0eda0c8..2914d4c68fea 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -436,7 +436,8 @@ static void target_release_session(struct kref *kref) struct se_session, sess_kref); struct se_portal_group *se_tpg = se_sess->se_tpg; - se_tpg->se_tpg_tfo->close_session(se_sess); + if (se_tpg->se_tpg_tfo->close_session) + se_tpg->se_tpg_tfo->close_session(se_sess); } int target_get_session(struct se_session *se_sess) diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index f4ed427e04ce..35fe3c80cfc0 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -1290,10 +1290,6 @@ static void usbg_release_cmd(struct se_cmd *se_cmd) percpu_ida_free(&se_sess->sess_tag_pool, se_cmd->map_tag); } -static void usbg_close_session(struct se_session *se_sess) -{ -} - static u32 usbg_sess_get_index(struct se_session *se_sess) { return 0; @@ -1730,7 +1726,6 @@ static const struct target_core_fabric_ops usbg_ops = { .tpg_check_prod_mode_write_protect = usbg_check_false, .tpg_get_inst_index = usbg_tpg_get_inst_index, .release_cmd = usbg_release_cmd, - .close_session = usbg_close_session, .sess_get_index = usbg_sess_get_index, .sess_get_initiator_sid = NULL, .write_pending = usbg_send_write_request, diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index d2c60e5c48b3..9d6320e8ff3e 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -333,11 +333,6 @@ static void vhost_scsi_release_cmd(struct se_cmd *se_cmd) percpu_ida_free(&se_sess->sess_tag_pool, se_cmd->map_tag); } -static void vhost_scsi_close_session(struct se_session *se_sess) -{ - return; -} - static u32 vhost_scsi_sess_get_index(struct se_session *se_sess) { return 0; @@ -2109,7 +2104,6 @@ static struct target_core_fabric_ops vhost_scsi_ops = { .tpg_get_inst_index = vhost_scsi_tpg_get_inst_index, .release_cmd = vhost_scsi_release_cmd, .check_stop_free = vhost_scsi_check_stop_free, - .close_session = vhost_scsi_close_session, .sess_get_index = vhost_scsi_sess_get_index, .sess_get_initiator_sid = NULL, .write_pending = vhost_scsi_write_pending, diff --git a/drivers/xen/xen-scsiback.c b/drivers/xen/xen-scsiback.c index 783947a272ab..d6950e0802b7 100644 --- a/drivers/xen/xen-scsiback.c +++ b/drivers/xen/xen-scsiback.c @@ -1399,10 +1399,6 @@ static void scsiback_release_cmd(struct se_cmd *se_cmd) percpu_ida_free(&se_sess->sess_tag_pool, se_cmd->map_tag); } -static void scsiback_close_session(struct se_session *se_sess) -{ -} - static u32 scsiback_sess_get_index(struct se_session *se_sess) { return 0; @@ -1836,7 +1832,6 @@ static const struct target_core_fabric_ops scsiback_ops = { .tpg_get_inst_index = scsiback_tpg_get_inst_index, .check_stop_free = scsiback_check_stop_free, .release_cmd = scsiback_release_cmd, - .close_session = scsiback_close_session, .sess_get_index = scsiback_sess_get_index, .sess_get_initiator_sid = NULL, .write_pending = scsiback_write_pending, From e3dc0e316559de8c0139bc84d82244e0907c4f17 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 May 2016 15:45:23 +0200 Subject: [PATCH 103/393] tcm_qla2xxx: introduce a private sess_kref This stops abusing the common sess_kref to overload it for private usage, which allows removing the shutdown_session method as well. Signed-off-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- drivers/scsi/qla2xxx/qla_target.c | 56 +++++++++++++++++------------- drivers/scsi/qla2xxx/qla_target.h | 4 +-- drivers/scsi/qla2xxx/tcm_qla2xxx.c | 39 ++------------------- 3 files changed, 35 insertions(+), 64 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c index 8a44d1541eb4..ca39deb4ff5b 100644 --- a/drivers/scsi/qla2xxx/qla_target.c +++ b/drivers/scsi/qla2xxx/qla_target.c @@ -637,8 +637,10 @@ static void qlt_free_session_done(struct work_struct *work) } /* ha->tgt.sess_lock supposed to be held on entry */ -void qlt_unreg_sess(struct qla_tgt_sess *sess) +static void qlt_release_session(struct kref *kref) { + struct qla_tgt_sess *sess = + container_of(kref, struct qla_tgt_sess, sess_kref); struct scsi_qla_host *vha = sess->vha; if (sess->se_sess) @@ -651,8 +653,16 @@ void qlt_unreg_sess(struct qla_tgt_sess *sess) INIT_WORK(&sess->free_work, qlt_free_session_done); schedule_work(&sess->free_work); } -EXPORT_SYMBOL(qlt_unreg_sess); +void qlt_put_sess(struct qla_tgt_sess *sess) +{ + if (!sess) + return; + + assert_spin_locked(&sess->vha->hw->tgt.sess_lock); + kref_put(&sess->sess_kref, qlt_release_session); +} +EXPORT_SYMBOL(qlt_put_sess); static int qlt_reset(struct scsi_qla_host *vha, void *iocb, int mcmd) { @@ -857,12 +867,9 @@ static void qlt_del_sess_work_fn(struct delayed_work *work) ql_dbg(ql_dbg_tgt_mgt, vha, 0xf004, "Timeout: sess %p about to be deleted\n", sess); - if (sess->se_sess) { + if (sess->se_sess) ha->tgt.tgt_ops->shutdown_sess(sess); - ha->tgt.tgt_ops->put_sess(sess); - } else { - qlt_unreg_sess(sess); - } + qlt_put_sess(sess); } else { schedule_delayed_work(&tgt->sess_del_work, sess->expires - elapsed); @@ -917,7 +924,7 @@ static struct qla_tgt_sess *qlt_create_sess( } } - kref_get(&sess->se_sess->sess_kref); + kref_get(&sess->sess_kref); ha->tgt.tgt_ops->update_sess(sess, fcport->d_id, fcport->loop_id, (fcport->flags & FCF_CONF_COMP_SUPPORTED)); @@ -947,6 +954,7 @@ static struct qla_tgt_sess *qlt_create_sess( sess->s_id = fcport->d_id; sess->loop_id = fcport->loop_id; sess->local = local; + kref_init(&sess->sess_kref); INIT_LIST_HEAD(&sess->del_list_entry); /* Under normal circumstances we want to logout from firmware when @@ -991,7 +999,7 @@ static struct qla_tgt_sess *qlt_create_sess( * Take an extra reference to ->sess_kref here to handle qla_tgt_sess * access across ->tgt.sess_lock reaquire. */ - kref_get(&sess->se_sess->sess_kref); + kref_get(&sess->sess_kref); } return sess; @@ -1035,7 +1043,7 @@ void qlt_fc_port_added(struct scsi_qla_host *vha, fc_port_t *fcport) spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return; } else { - kref_get(&sess->se_sess->sess_kref); + kref_get(&sess->sess_kref); if (sess->deleted) { qlt_undelete_sess(sess); @@ -1060,7 +1068,7 @@ void qlt_fc_port_added(struct scsi_qla_host *vha, fc_port_t *fcport) fcport->port_name, sess->loop_id); sess->local = 0; } - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } @@ -3817,7 +3825,7 @@ static void __qlt_do_work(struct qla_tgt_cmd *cmd) * Drop extra session reference from qla_tgt_handle_cmd_for_atio*( */ spin_lock_irqsave(&ha->tgt.sess_lock, flags); - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return; @@ -3836,7 +3844,7 @@ static void __qlt_do_work(struct qla_tgt_cmd *cmd) spin_unlock_irqrestore(&ha->hardware_lock, flags); spin_lock_irqsave(&ha->tgt.sess_lock, flags); - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } @@ -3936,13 +3944,13 @@ static void qlt_create_sess_from_atio(struct work_struct *work) if (!cmd) { spin_lock_irqsave(&ha->hardware_lock, flags); qlt_send_busy(vha, &op->atio, SAM_STAT_BUSY); - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->hardware_lock, flags); kfree(op); return; } /* - * __qlt_do_work() will call ha->tgt.tgt_ops->put_sess() to release + * __qlt_do_work() will call qlt_put_sess() to release * the extra reference taken above by qlt_make_local_sess() */ __qlt_do_work(cmd); @@ -4003,13 +4011,13 @@ static int qlt_handle_cmd_for_atio(struct scsi_qla_host *vha, /* * Do kref_get() before returning + dropping qla_hw_data->hardware_lock. */ - kref_get(&sess->se_sess->sess_kref); + kref_get(&sess->sess_kref); cmd = qlt_get_tag(vha, sess, atio); if (!cmd) { ql_dbg(ql_dbg_io, vha, 0x3062, "qla_target(%d): Allocation of cmd failed\n", vha->vp_idx); - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); return -ENOMEM; } @@ -5911,7 +5919,7 @@ static void qlt_abort_work(struct qla_tgt *tgt, goto out_term2; } - kref_get(&sess->se_sess->sess_kref); + kref_get(&sess->sess_kref); } spin_lock_irqsave(&ha->hardware_lock, flags); @@ -5924,7 +5932,7 @@ static void qlt_abort_work(struct qla_tgt *tgt, goto out_term; spin_unlock_irqrestore(&ha->hardware_lock, flags); - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->tgt.sess_lock, flags2); return; @@ -5935,8 +5943,7 @@ static void qlt_abort_work(struct qla_tgt *tgt, qlt_24xx_send_abts_resp(vha, &prm->abts, FCP_TMF_REJECTED, false); spin_unlock_irqrestore(&ha->hardware_lock, flags); - if (sess) - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->tgt.sess_lock, flags2); } @@ -5976,7 +5983,7 @@ static void qlt_tmr_work(struct qla_tgt *tgt, goto out_term; } - kref_get(&sess->se_sess->sess_kref); + kref_get(&sess->sess_kref); } iocb = a; @@ -5988,14 +5995,13 @@ static void qlt_tmr_work(struct qla_tgt *tgt, if (rc != 0) goto out_term; - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return; out_term: qlt_send_term_exchange(vha, NULL, &prm->tm_iocb2, 1, 0); - if (sess) - ha->tgt.tgt_ops->put_sess(sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } diff --git a/drivers/scsi/qla2xxx/qla_target.h b/drivers/scsi/qla2xxx/qla_target.h index d857feeb6514..f26c5f60eedd 100644 --- a/drivers/scsi/qla2xxx/qla_target.h +++ b/drivers/scsi/qla2xxx/qla_target.h @@ -738,7 +738,6 @@ struct qla_tgt_func_tmpl { struct qla_tgt_sess *(*find_sess_by_s_id)(struct scsi_qla_host *, const uint8_t *); void (*clear_nacl_from_fcport_map)(struct qla_tgt_sess *); - void (*put_sess)(struct qla_tgt_sess *); void (*shutdown_sess)(struct qla_tgt_sess *); }; @@ -930,6 +929,7 @@ struct qla_tgt_sess { int generation; struct se_session *se_sess; + struct kref sess_kref; struct scsi_qla_host *vha; struct qla_tgt *tgt; @@ -1101,7 +1101,7 @@ extern int qlt_remove_target(struct qla_hw_data *, struct scsi_qla_host *); extern int qlt_lport_register(void *, u64, u64, u64, int (*callback)(struct scsi_qla_host *, void *, u64, u64)); extern void qlt_lport_deregister(struct scsi_qla_host *); -extern void qlt_unreg_sess(struct qla_tgt_sess *); +void qlt_put_sess(struct qla_tgt_sess *sess); extern void qlt_fc_port_added(struct scsi_qla_host *, fc_port_t *); extern void qlt_fc_port_deleted(struct scsi_qla_host *, fc_port_t *, int); extern int __init qlt_init(void); diff --git a/drivers/scsi/qla2xxx/tcm_qla2xxx.c b/drivers/scsi/qla2xxx/tcm_qla2xxx.c index c1461d225f08..675f75bafc3c 100644 --- a/drivers/scsi/qla2xxx/tcm_qla2xxx.c +++ b/drivers/scsi/qla2xxx/tcm_qla2xxx.c @@ -339,22 +339,6 @@ static void tcm_qla2xxx_release_cmd(struct se_cmd *se_cmd) qlt_free_cmd(cmd); } -static int tcm_qla2xxx_shutdown_session(struct se_session *se_sess) -{ - struct qla_tgt_sess *sess = se_sess->fabric_sess_ptr; - struct scsi_qla_host *vha; - unsigned long flags; - - BUG_ON(!sess); - vha = sess->vha; - - spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags); - target_sess_cmd_list_set_waiting(se_sess); - spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); - - return 1; -} - static void tcm_qla2xxx_close_session(struct se_session *se_sess) { struct qla_tgt_sess *sess = se_sess->fabric_sess_ptr; @@ -365,7 +349,8 @@ static void tcm_qla2xxx_close_session(struct se_session *se_sess) vha = sess->vha; spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags); - qlt_unreg_sess(sess); + target_sess_cmd_list_set_waiting(se_sess); + qlt_put_sess(sess); spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); } @@ -758,23 +743,6 @@ static void tcm_qla2xxx_clear_nacl_from_fcport_map(struct qla_tgt_sess *sess) tcm_qla2xxx_clear_sess_lookup(lport, nacl, sess); } -static void tcm_qla2xxx_release_session(struct kref *kref) -{ - struct se_session *se_sess = container_of(kref, - struct se_session, sess_kref); - - qlt_unreg_sess(se_sess->fabric_sess_ptr); -} - -static void tcm_qla2xxx_put_sess(struct qla_tgt_sess *sess) -{ - if (!sess) - return; - - assert_spin_locked(&sess->vha->hw->tgt.sess_lock); - kref_put(&sess->se_sess->sess_kref, tcm_qla2xxx_release_session); -} - static void tcm_qla2xxx_shutdown_sess(struct qla_tgt_sess *sess) { assert_spin_locked(&sess->vha->hw->tgt.sess_lock); @@ -1579,7 +1547,6 @@ static struct qla_tgt_func_tmpl tcm_qla2xxx_template = { .find_sess_by_s_id = tcm_qla2xxx_find_sess_by_s_id, .find_sess_by_loop_id = tcm_qla2xxx_find_sess_by_loop_id, .clear_nacl_from_fcport_map = tcm_qla2xxx_clear_nacl_from_fcport_map, - .put_sess = tcm_qla2xxx_put_sess, .shutdown_sess = tcm_qla2xxx_shutdown_sess, }; @@ -1847,7 +1814,6 @@ static const struct target_core_fabric_ops tcm_qla2xxx_ops = { .tpg_get_inst_index = tcm_qla2xxx_tpg_get_inst_index, .check_stop_free = tcm_qla2xxx_check_stop_free, .release_cmd = tcm_qla2xxx_release_cmd, - .shutdown_session = tcm_qla2xxx_shutdown_session, .close_session = tcm_qla2xxx_close_session, .sess_get_index = tcm_qla2xxx_sess_get_index, .sess_get_initiator_sid = NULL, @@ -1890,7 +1856,6 @@ static const struct target_core_fabric_ops tcm_qla2xxx_npiv_ops = { .tpg_get_inst_index = tcm_qla2xxx_tpg_get_inst_index, .check_stop_free = tcm_qla2xxx_check_stop_free, .release_cmd = tcm_qla2xxx_release_cmd, - .shutdown_session = tcm_qla2xxx_shutdown_session, .close_session = tcm_qla2xxx_close_session, .sess_get_index = tcm_qla2xxx_sess_get_index, .sess_get_initiator_sid = NULL, From 44f33d0fb5a7181cc34529c211d7b5039d8a0a4b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 May 2016 15:45:24 +0200 Subject: [PATCH 104/393] iscsi-target: remove usage of ->shutdown_session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ->shutdown session only decideѕ if the target core calls ->close_session directly, or if the fabrics drivers calls it manually later through target_put_session, which at this point will always close the session as it has been removed from the lookup list and thus no new references will be acquired from the core. So instead remove ->shutdown and have the core call ->close_session directly, and replace all calls to target_put_session in the iSCSI target with direct calls to iscsit_close_session. Signed-off-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 10 +++++++--- drivers/target/iscsi/iscsi_target_configfs.c | 19 ++----------------- drivers/target/iscsi/iscsi_target_erl0.c | 2 +- drivers/target/iscsi/iscsi_target_login.c | 4 ++-- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 487d8f37b23a..9a4f8957725b 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -4270,7 +4270,7 @@ int iscsit_close_connection( if (!atomic_read(&sess->session_reinstatement) && atomic_read(&sess->session_fall_back_to_erl0)) { spin_unlock_bh(&sess->conn_lock); - target_put_session(sess->se_sess); + iscsit_close_session(sess); return 0; } else if (atomic_read(&sess->session_logout)) { @@ -4299,6 +4299,10 @@ int iscsit_close_connection( } } +/* + * If the iSCSI Session for the iSCSI Initiator Node exists, + * forcefully shutdown the iSCSI NEXUS. + */ int iscsit_close_session(struct iscsi_session *sess) { struct iscsi_portal_group *tpg = sess->tpg; @@ -4398,7 +4402,7 @@ static void iscsit_logout_post_handler_closesession( iscsit_dec_conn_usage_count(conn); iscsit_stop_session(sess, sleep, sleep); iscsit_dec_session_usage_count(sess); - target_put_session(sess->se_sess); + iscsit_close_session(sess); } static void iscsit_logout_post_handler_samecid( @@ -4570,7 +4574,7 @@ int iscsit_free_session(struct iscsi_session *sess) } else spin_unlock_bh(&sess->conn_lock); - target_put_session(sess->se_sess); + iscsit_close_session(sess); return 0; } diff --git a/drivers/target/iscsi/iscsi_target_configfs.c b/drivers/target/iscsi/iscsi_target_configfs.c index 3565e07ad8a6..1b8f885dbfeb 100644 --- a/drivers/target/iscsi/iscsi_target_configfs.c +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -1630,7 +1630,7 @@ static int lio_tpg_check_prot_fabric_only( * This function calls iscsit_inc_session_usage_count() on the * struct iscsi_session in question. */ -static int lio_tpg_shutdown_session(struct se_session *se_sess) +static void lio_tpg_close_session(struct se_session *se_sess) { struct iscsi_session *sess = se_sess->fabric_sess_ptr; struct se_portal_group *se_tpg = &sess->tpg->tpg_se_tpg; @@ -1642,7 +1642,7 @@ static int lio_tpg_shutdown_session(struct se_session *se_sess) (sess->time2retain_timer_flags & ISCSI_TF_EXPIRED)) { spin_unlock(&sess->conn_lock); spin_unlock_bh(&se_tpg->session_lock); - return 0; + return; } atomic_set(&sess->session_reinstatement, 1); spin_unlock(&sess->conn_lock); @@ -1651,20 +1651,6 @@ static int lio_tpg_shutdown_session(struct se_session *se_sess) spin_unlock_bh(&se_tpg->session_lock); iscsit_stop_session(sess, 1, 1); - return 1; -} - -/* - * Calls iscsit_dec_session_usage_count() as inverse of - * lio_tpg_shutdown_session() - */ -static void lio_tpg_close_session(struct se_session *se_sess) -{ - struct iscsi_session *sess = se_sess->fabric_sess_ptr; - /* - * If the iSCSI Session for the iSCSI Initiator Node exists, - * forcefully shutdown the iSCSI NEXUS. - */ iscsit_close_session(sess); } @@ -1716,7 +1702,6 @@ const struct target_core_fabric_ops iscsi_ops = { .tpg_get_inst_index = lio_tpg_get_inst_index, .check_stop_free = lio_check_stop_free, .release_cmd = lio_release_cmd, - .shutdown_session = lio_tpg_shutdown_session, .close_session = lio_tpg_close_session, .sess_get_index = lio_sess_get_index, .sess_get_initiator_sid = lio_sess_get_initiator_sid, diff --git a/drivers/target/iscsi/iscsi_target_erl0.c b/drivers/target/iscsi/iscsi_target_erl0.c index 210f6e4830e3..b54e72c7ab0f 100644 --- a/drivers/target/iscsi/iscsi_target_erl0.c +++ b/drivers/target/iscsi/iscsi_target_erl0.c @@ -786,7 +786,7 @@ static void iscsit_handle_time2retain_timeout(unsigned long data) } spin_unlock_bh(&se_tpg->session_lock); - target_put_session(sess->se_sess); + iscsit_close_session(sess); } void iscsit_start_time2retain_handler(struct iscsi_session *sess) diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c index a09498f16690..b5212f0f9571 100644 --- a/drivers/target/iscsi/iscsi_target_login.c +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -228,7 +228,7 @@ int iscsi_check_for_session_reinstatement(struct iscsi_conn *conn) if (sess->session_state == TARG_SESS_STATE_FAILED) { spin_unlock_bh(&sess->conn_lock); iscsit_dec_session_usage_count(sess); - target_put_session(sess->se_sess); + iscsit_close_session(sess); return 0; } spin_unlock_bh(&sess->conn_lock); @@ -236,7 +236,7 @@ int iscsi_check_for_session_reinstatement(struct iscsi_conn *conn) iscsit_stop_session(sess, 1, 1); iscsit_dec_session_usage_count(sess); - target_put_session(sess->se_sess); + iscsit_close_session(sess); return 0; } From d94331fab6d4d48280a5cea646352db90777966d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 May 2016 15:45:25 +0200 Subject: [PATCH 105/393] target: remove sess_kref and ->shutdown_session Both of them are unused now that drivers handle any delayed session shutdown internally. Signed-off-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- drivers/target/target_core_tpg.c | 14 +++----------- drivers/target/target_core_transport.c | 23 ----------------------- include/target/target_core_base.h | 1 - include/target/target_core_fabric.h | 6 ------ 4 files changed, 3 insertions(+), 41 deletions(-) diff --git a/drivers/target/target_core_tpg.c b/drivers/target/target_core_tpg.c index a81a37045403..d99752c6cd60 100644 --- a/drivers/target/target_core_tpg.c +++ b/drivers/target/target_core_tpg.c @@ -340,26 +340,18 @@ static void target_shutdown_sessions(struct se_node_acl *acl) { struct se_session *sess; unsigned long flags; - int ret; restart: spin_lock_irqsave(&acl->nacl_sess_lock, flags); list_for_each_entry(sess, &acl->acl_sess_list, sess_acl_list) { if (sess->sess_tearing_down) continue; - if (!target_get_session(sess)) - continue; list_del_init(&sess->sess_acl_list); - spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); - if (acl->se_tpg->se_tpg_tfo->shutdown_session) - ret = acl->se_tpg->se_tpg_tfo->shutdown_session(sess); - else - ret = 1; - target_put_session(sess); - if (ret) - target_put_session(sess); + + if (acl->se_tpg->se_tpg_tfo->close_session) + acl->se_tpg->se_tpg_tfo->close_session(sess); goto restart; } spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 2914d4c68fea..6c089afe553a 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -239,7 +239,6 @@ struct se_session *transport_init_session(enum target_prot_op sup_prot_ops) INIT_LIST_HEAD(&se_sess->sess_cmd_list); INIT_LIST_HEAD(&se_sess->sess_wait_list); spin_lock_init(&se_sess->sess_cmd_lock); - kref_init(&se_sess->sess_kref); se_sess->sup_prot_ops = sup_prot_ops; return se_sess; @@ -430,28 +429,6 @@ target_alloc_session(struct se_portal_group *tpg, } EXPORT_SYMBOL(target_alloc_session); -static void target_release_session(struct kref *kref) -{ - struct se_session *se_sess = container_of(kref, - struct se_session, sess_kref); - struct se_portal_group *se_tpg = se_sess->se_tpg; - - if (se_tpg->se_tpg_tfo->close_session) - se_tpg->se_tpg_tfo->close_session(se_sess); -} - -int target_get_session(struct se_session *se_sess) -{ - return kref_get_unless_zero(&se_sess->sess_kref); -} -EXPORT_SYMBOL(target_get_session); - -void target_put_session(struct se_session *se_sess) -{ - kref_put(&se_sess->sess_kref, target_release_session); -} -EXPORT_SYMBOL(target_put_session); - ssize_t target_show_dynamic_sessions(struct se_portal_group *se_tpg, char *page) { struct se_session *se_sess; diff --git a/include/target/target_core_base.h b/include/target/target_core_base.h index 1d4e1cb7f373..b316b44d03f3 100644 --- a/include/target/target_core_base.h +++ b/include/target/target_core_base.h @@ -602,7 +602,6 @@ struct se_session { struct list_head sess_cmd_list; struct list_head sess_wait_list; spinlock_t sess_cmd_lock; - struct kref sess_kref; void *sess_cmd_map; struct percpu_ida sess_tag_pool; }; diff --git a/include/target/target_core_fabric.h b/include/target/target_core_fabric.h index 8ff6d40a294f..ec79da3b43df 100644 --- a/include/target/target_core_fabric.h +++ b/include/target/target_core_fabric.h @@ -50,10 +50,6 @@ struct target_core_fabric_ops { */ int (*check_stop_free)(struct se_cmd *); void (*release_cmd)(struct se_cmd *); - /* - * Called with spin_lock_bh(struct se_portal_group->session_lock held. - */ - int (*shutdown_session)(struct se_session *); void (*close_session)(struct se_session *); u32 (*sess_get_index)(struct se_session *); /* @@ -123,8 +119,6 @@ void __transport_register_session(struct se_portal_group *, struct se_node_acl *, struct se_session *, void *); void transport_register_session(struct se_portal_group *, struct se_node_acl *, struct se_session *, void *); -int target_get_session(struct se_session *); -void target_put_session(struct se_session *); ssize_t target_show_dynamic_sessions(struct se_portal_group *, char *); void transport_free_session(struct se_session *); void target_put_nacl(struct se_node_acl *); From cb7911694771faf16930b72a104d3cd41d500e2c Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 6 May 2016 00:16:17 +0100 Subject: [PATCH 106/393] target: need_to_release is always false, remove redundant check and kfree The removal the #ifdef'd code on CONFIG_ARCH_HAS_SG_CHAIN in commit 02c4de53add1b79 ("target/rd: always chain S/G list") removed the setting of need_to_release to true, so we are now left with need_to_release always being false and hence the conditional kfree is never reached. Clean up the dead code by removing need_to_release and the conditional kfree. Signed-off-by: Colin Ian King Reviewed-by: Christoph Hellwig Signed-off-by: Nicholas Bellinger --- drivers/target/target_core_rd.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/target/target_core_rd.c b/drivers/target/target_core_rd.c index 47a833f3a145..24b36fd785f1 100644 --- a/drivers/target/target_core_rd.c +++ b/drivers/target/target_core_rd.c @@ -403,7 +403,6 @@ static sense_reason_t rd_do_prot_rw(struct se_cmd *cmd, bool is_read) struct se_device *se_dev = cmd->se_dev; struct rd_dev *dev = RD_DEV(se_dev); struct rd_dev_sg_table *prot_table; - bool need_to_release = false; struct scatterlist *prot_sg; u32 sectors = cmd->data_length / se_dev->dev_attrib.block_size; u32 prot_offset, prot_page; @@ -432,9 +431,6 @@ static sense_reason_t rd_do_prot_rw(struct se_cmd *cmd, bool is_read) if (!rc) sbc_dif_copy_prot(cmd, sectors, is_read, prot_sg, prot_offset); - if (need_to_release) - kfree(prot_sg); - return rc; } From 2b16509c5f26d6c160bd89164cf18f18d713daf6 Mon Sep 17 00:00:00 2001 From: Imran Haider Date: Sun, 8 May 2016 11:17:54 -0400 Subject: [PATCH 107/393] iscsi-target: graceful disconnect on invalid mapping to iovec Ensures the first page entry is within bounds. A failed check would terminate the iSCSI connection instead of causing a NULL-dereference. This violation seems to happen with certain iSCSI commands where the computed CDB length is zero but the expected transfer length is non-zero. The real problem is probably on the iSCSI initiator side since there is a discrepancy between the iSCSI header and the encapsulated CDB Opcode. Signed-off-by: Imran Haider Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 9a4f8957725b..bc743d2a71a2 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -904,7 +904,14 @@ static int iscsit_map_iovec( /* * We know each entry in t_data_sg contains a page. */ - sg = &cmd->se_cmd.t_data_sg[data_offset / PAGE_SIZE]; + u32 ent = data_offset / PAGE_SIZE; + + if (ent >= cmd->se_cmd.t_data_nents) { + pr_err("Initial page entry out-of-bounds\n"); + return -1; + } + + sg = &cmd->se_cmd.t_data_sg[ent]; page_off = (data_offset % PAGE_SIZE); cmd->first_data_sg = sg; From 54a5e73f4d6e31d5628e08b4d575c71d71670e09 Mon Sep 17 00:00:00 2001 From: Laurence Oberman Date: Tue, 10 May 2016 01:23:17 -0700 Subject: [PATCH 108/393] tcm_qla2xxx Add SCSI command jammer/discard capability This patch introduces support for TCM_QLA2XXX_DEBUG and associated tcm_qla2xxx_tpg->tpg_attrib.jam_host checking used to test dropped packets to FC host environments. Tested by: Laurence Oberman Signed-off-by: Laurence Oberman Acked-by: Himanshu Madhani Signed-off-by: Nicholas Bellinger --- Documentation/scsi/tcm_qla2xxx.txt | 22 ++++++++++++++++++++++ drivers/scsi/qla2xxx/Kconfig | 9 +++++++++ drivers/scsi/qla2xxx/tcm_qla2xxx.c | 20 ++++++++++++++++++++ drivers/scsi/qla2xxx/tcm_qla2xxx.h | 1 + 4 files changed, 52 insertions(+) create mode 100644 Documentation/scsi/tcm_qla2xxx.txt diff --git a/Documentation/scsi/tcm_qla2xxx.txt b/Documentation/scsi/tcm_qla2xxx.txt new file mode 100644 index 000000000000..c3a670a25e2b --- /dev/null +++ b/Documentation/scsi/tcm_qla2xxx.txt @@ -0,0 +1,22 @@ +tcm_qla2xxx jam_host attribute +------------------------------ +There is now a new module endpoint atribute called jam_host +attribute: jam_host: boolean=0/1 +This attribute and accompanying code is only included if the +Kconfig parameter TCM_QLA2XXX_DEBUG is set to Y +By default this jammer code and functionality is disabled + +Use this attribute to control the discarding of SCSI commands to a +selected host. +This may be useful for testing error handling and simulating slow drain +and other fabric issues. + +Setting a boolean of 1 for the jam_host attribute for a particular host + will discard the commands for that host. +Reset back to 0 to stop the jamming. + +Enable host 4 to be jammed +echo 1 > /sys/kernel/config/target/qla2xxx/21:00:00:24:ff:27:8f:ae/tpgt_1/attrib/jam_host + +Disable jamming on host 4 +echo 0 > /sys/kernel/config/target/qla2xxx/21:00:00:24:ff:27:8f:ae/tpgt_1/attrib/jam_host diff --git a/drivers/scsi/qla2xxx/Kconfig b/drivers/scsi/qla2xxx/Kconfig index 10aa18ba05fd..67c0d5aa3212 100644 --- a/drivers/scsi/qla2xxx/Kconfig +++ b/drivers/scsi/qla2xxx/Kconfig @@ -36,3 +36,12 @@ config TCM_QLA2XXX default n ---help--- Say Y here to enable the TCM_QLA2XXX fabric module for QLogic 24xx+ series target mode HBAs + +if TCM_QLA2XXX +config TCM_QLA2XXX_DEBUG + bool "TCM_QLA2XXX fabric module DEBUG mode for QLogic 24xx+ series target mode HBAs" + default n + ---help--- + Say Y here to enable the TCM_QLA2XXX fabric module DEBUG for QLogic 24xx+ series target mode HBAs + This will include code to enable the SCSI command jammer +endif diff --git a/drivers/scsi/qla2xxx/tcm_qla2xxx.c b/drivers/scsi/qla2xxx/tcm_qla2xxx.c index 675f75bafc3c..6643f6fc7795 100644 --- a/drivers/scsi/qla2xxx/tcm_qla2xxx.c +++ b/drivers/scsi/qla2xxx/tcm_qla2xxx.c @@ -442,6 +442,10 @@ static int tcm_qla2xxx_handle_cmd(scsi_qla_host_t *vha, struct qla_tgt_cmd *cmd, struct se_cmd *se_cmd = &cmd->se_cmd; struct se_session *se_sess; struct qla_tgt_sess *sess; +#ifdef CONFIG_TCM_QLA2XXX_DEBUG + struct se_portal_group *se_tpg; + struct tcm_qla2xxx_tpg *tpg; +#endif int flags = TARGET_SCF_ACK_KREF; if (bidi) @@ -462,6 +466,15 @@ static int tcm_qla2xxx_handle_cmd(scsi_qla_host_t *vha, struct qla_tgt_cmd *cmd, return -EINVAL; } +#ifdef CONFIG_TCM_QLA2XXX_DEBUG + se_tpg = se_sess->se_tpg; + tpg = container_of(se_tpg, struct tcm_qla2xxx_tpg, se_tpg); + if (unlikely(tpg->tpg_attrib.jam_host)) { + /* return, and dont run target_submit_cmd,discarding command */ + return 0; + } +#endif + cmd->vha->tgt_counters.qla_core_sbt_cmd++; return target_submit_cmd(se_cmd, se_sess, cdb, &cmd->sense_buffer[0], cmd->unpacked_lun, data_length, fcp_task_attr, @@ -812,6 +825,9 @@ DEF_QLA_TPG_ATTRIB(cache_dynamic_acls); DEF_QLA_TPG_ATTRIB(demo_mode_write_protect); DEF_QLA_TPG_ATTRIB(prod_mode_write_protect); DEF_QLA_TPG_ATTRIB(demo_mode_login_only); +#ifdef CONFIG_TCM_QLA2XXX_DEBUG +DEF_QLA_TPG_ATTRIB(jam_host); +#endif static struct configfs_attribute *tcm_qla2xxx_tpg_attrib_attrs[] = { &tcm_qla2xxx_tpg_attrib_attr_generate_node_acls, @@ -819,6 +835,9 @@ static struct configfs_attribute *tcm_qla2xxx_tpg_attrib_attrs[] = { &tcm_qla2xxx_tpg_attrib_attr_demo_mode_write_protect, &tcm_qla2xxx_tpg_attrib_attr_prod_mode_write_protect, &tcm_qla2xxx_tpg_attrib_attr_demo_mode_login_only, +#ifdef CONFIG_TCM_QLA2XXX_DEBUG + &tcm_qla2xxx_tpg_attrib_attr_jam_host, +#endif NULL, }; @@ -991,6 +1010,7 @@ static struct se_portal_group *tcm_qla2xxx_make_tpg( tpg->tpg_attrib.demo_mode_write_protect = 1; tpg->tpg_attrib.cache_dynamic_acls = 1; tpg->tpg_attrib.demo_mode_login_only = 1; + tpg->tpg_attrib.jam_host = 0; ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_FCP); if (ret < 0) { diff --git a/drivers/scsi/qla2xxx/tcm_qla2xxx.h b/drivers/scsi/qla2xxx/tcm_qla2xxx.h index 3bbf4cb6fd97..37e026a4823d 100644 --- a/drivers/scsi/qla2xxx/tcm_qla2xxx.h +++ b/drivers/scsi/qla2xxx/tcm_qla2xxx.h @@ -34,6 +34,7 @@ struct tcm_qla2xxx_tpg_attrib { int prod_mode_write_protect; int demo_mode_login_only; int fabric_prot_type; + int jam_host; }; struct tcm_qla2xxx_tpg { From ee057d2ee73259f455cfbb7a4db808fc6b6405dd Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 10 May 2016 02:22:37 +0000 Subject: [PATCH 109/393] ASoC: rsnd: don't use prohibited number to PDMACHCRn.SRS Current rsnd_dmapp_get_id() returns 0xFF as error code if system used strange connection. It will be used as PDMACHCRn.SRS, but 0xFF is prohibited number. In order not to use prohibited number, this patch indicates error message and returns 0x00 (same as SSI00) in error case. Special thanks to Dung-san. Reported-by: Nguyen Viet Dung Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- sound/soc/sh/rcar/dma.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c index 7658e8fd7bdc..6bc93cbb3049 100644 --- a/sound/soc/sh/rcar/dma.c +++ b/sound/soc/sh/rcar/dma.c @@ -316,11 +316,15 @@ static u32 rsnd_dmapp_get_id(struct rsnd_dai_stream *io, size = ARRAY_SIZE(gen2_id_table_cmd); } - if (!entry) - return 0xFF; + if ((!entry) || (size <= id)) { + struct device *dev = rsnd_priv_to_dev(rsnd_io_to_priv(io)); - if (size <= id) - return 0xFF; + dev_err(dev, "unknown connection (%s[%d])\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + + /* use non-prohibited SRS number as error */ + return 0x00; /* SSI00 */ + } return entry[id]; } From 1135ef1139b3ebd5dc762b6b02384f8a7a84f8d4 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 11 May 2016 14:14:05 +0300 Subject: [PATCH 110/393] ASoC: twl6040: Select LPPLL during standby When the codec is in standby we do not need to keep the HPPLL active. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl6040.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index bc3de2e844e6..7a744b581cc1 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -824,7 +824,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, { struct twl6040 *twl6040 = codec->control_data; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); - int ret; + int ret = 0; switch (level) { case SND_SOC_BIAS_ON: @@ -832,12 +832,16 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - if (priv->codec_powered) + if (priv->codec_powered) { + /* Select low power PLL in standby */ + ret = twl6040_set_pll(twl6040, TWL6040_SYSCLK_SEL_LPPLL, + 32768, 19200000); break; + } ret = twl6040_power(twl6040, 1); if (ret) - return ret; + break; priv->codec_powered = 1; @@ -853,7 +857,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, break; } - return 0; + return ret; } static int twl6040_startup(struct snd_pcm_substream *substream, From af37d21a3245c4a54d608c1d85588203ebfe1ef9 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Wed, 11 May 2016 11:48:11 +0800 Subject: [PATCH 111/393] ASoC: max98371 Remove duplicate entry in max98371_reg Signed-off-by: Axel Lin Signed-off-by: Mark Brown --- sound/soc/codecs/max98371.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c index 21fae0932137..cf0a39bb631a 100644 --- a/sound/soc/codecs/max98371.c +++ b/sound/soc/codecs/max98371.c @@ -107,7 +107,6 @@ static const struct reg_default max98371_reg[] = { { 0x39, 0x00 }, { 0x3A, 0x00 }, { 0x3B, 0x00 }, - { 0x3B, 0x00 }, { 0x3C, 0x00 }, { 0x3D, 0x00 }, { 0x3E, 0x00 }, From f84e63f2ffed8a12ba58a154dc17a5ed6d31d61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Ol=C5=A1=C3=A1k?= Date: Thu, 28 Apr 2016 14:32:44 +0200 Subject: [PATCH 112/393] drm/amdgpu: fix TC cache flushing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TC_WB_ACTION must be set according to the docs Signed-off-by: Marek Olšák Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 4 +++- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 1dab5f2b725b..0dee008bcc57 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -50,9 +50,11 @@ * KMS wrapper. * - 3.0.0 - initial driver * - 3.1.0 - allow reading more status registers (GRBM, SRBM, SDMA, CP) + * - 3.2.0 - GFX8: Uses EOP_TC_WB_ACTION_EN, so UMDs don't have to do the same + * at the end of IBs. */ #define KMS_DRIVER_MAJOR 3 -#define KMS_DRIVER_MINOR 1 +#define KMS_DRIVER_MINOR 2 #define KMS_DRIVER_PATCHLEVEL 0 int amdgpu_vram_limit = 0; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 92647fbf5b8b..ef192aad7d2f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -5725,6 +5725,7 @@ static void gfx_v8_0_ring_emit_fence_gfx(struct amdgpu_ring *ring, u64 addr, amdgpu_ring_write(ring, PACKET3(PACKET3_EVENT_WRITE_EOP, 4)); amdgpu_ring_write(ring, (EOP_TCL1_ACTION_EN | EOP_TC_ACTION_EN | + EOP_TC_WB_ACTION_EN | EVENT_TYPE(CACHE_FLUSH_AND_INV_TS_EVENT) | EVENT_INDEX(5))); amdgpu_ring_write(ring, addr & 0xfffffffc); From f4833c4fd4d76ddee9e647814a64251801844db7 Mon Sep 17 00:00:00 2001 From: Harish Kasiviswanathan Date: Thu, 21 Apr 2016 10:40:18 -0400 Subject: [PATCH 113/393] drm/amdgpu: Encapsulate some VM table update parameters (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bundle some VM table parameters into amdgpu_vm_update_params structure, so that number of function parameters can be reduced. Only structural change, no logic change. v2: agd: squash in fix from Harish Signed-off-by: Harish Kasiviswanathan Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 111 ++++++++++++++----------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index ea708cb94862..9f36ed30ba11 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -53,6 +53,18 @@ /* Special value that no flush is necessary */ #define AMDGPU_VM_NO_FLUSH (~0ll) +/* Local structure. Encapsulate some VM table update parameters to reduce + * the number of function parameters + */ +struct amdgpu_vm_update_params { + /* address where to copy page table entries from */ + uint64_t src; + /* DMA addresses to use for mapping */ + dma_addr_t *pages_addr; + /* indirect buffer to fill with commands */ + struct amdgpu_ib *ib; +}; + /** * amdgpu_vm_num_pde - return the number of page directory entries * @@ -389,9 +401,7 @@ struct amdgpu_bo_va *amdgpu_vm_bo_find(struct amdgpu_vm *vm, * amdgpu_vm_update_pages - helper to call the right asic function * * @adev: amdgpu_device pointer - * @src: address where to copy page table entries from - * @pages_addr: DMA addresses to use for mapping - * @ib: indirect buffer to fill with commands + * @vm_update_params: see amdgpu_vm_update_params definition * @pe: addr of the page entry * @addr: dst addr to write into pe * @count: number of page entries to update @@ -402,29 +412,29 @@ struct amdgpu_bo_va *amdgpu_vm_bo_find(struct amdgpu_vm *vm, * to setup the page table using the DMA. */ static void amdgpu_vm_update_pages(struct amdgpu_device *adev, - uint64_t src, - dma_addr_t *pages_addr, - struct amdgpu_ib *ib, + struct amdgpu_vm_update_params + *vm_update_params, uint64_t pe, uint64_t addr, unsigned count, uint32_t incr, uint32_t flags) { trace_amdgpu_vm_set_page(pe, addr, count, incr, flags); - if (src) { - src += (addr >> 12) * 8; - amdgpu_vm_copy_pte(adev, ib, pe, src, count); + if (vm_update_params->src) { + amdgpu_vm_copy_pte(adev, vm_update_params->ib, + pe, (vm_update_params->src + (addr >> 12) * 8), count); - } else if (pages_addr) { - amdgpu_vm_write_pte(adev, ib, pages_addr, pe, addr, - count, incr, flags); + } else if (vm_update_params->pages_addr) { + amdgpu_vm_write_pte(adev, vm_update_params->ib, + vm_update_params->pages_addr, + pe, addr, count, incr, flags); } else if (count < 3) { - amdgpu_vm_write_pte(adev, ib, NULL, pe, addr, + amdgpu_vm_write_pte(adev, vm_update_params->ib, NULL, pe, addr, count, incr, flags); } else { - amdgpu_vm_set_pte_pde(adev, ib, pe, addr, + amdgpu_vm_set_pte_pde(adev, vm_update_params->ib, pe, addr, count, incr, flags); } } @@ -444,10 +454,12 @@ static int amdgpu_vm_clear_bo(struct amdgpu_device *adev, struct amdgpu_ring *ring; struct fence *fence = NULL; struct amdgpu_job *job; + struct amdgpu_vm_update_params vm_update_params; unsigned entries; uint64_t addr; int r; + memset(&vm_update_params, 0, sizeof(vm_update_params)); ring = container_of(vm->entity.sched, struct amdgpu_ring, sched); r = reservation_object_reserve_shared(bo->tbo.resv); @@ -465,7 +477,8 @@ static int amdgpu_vm_clear_bo(struct amdgpu_device *adev, if (r) goto error; - amdgpu_vm_update_pages(adev, 0, NULL, &job->ibs[0], addr, 0, entries, + vm_update_params.ib = &job->ibs[0]; + amdgpu_vm_update_pages(adev, &vm_update_params, addr, 0, entries, 0, 0); amdgpu_ring_pad_ib(ring, &job->ibs[0]); @@ -538,11 +551,12 @@ int amdgpu_vm_update_page_directory(struct amdgpu_device *adev, uint64_t last_pde = ~0, last_pt = ~0; unsigned count = 0, pt_idx, ndw; struct amdgpu_job *job; - struct amdgpu_ib *ib; + struct amdgpu_vm_update_params vm_update_params; struct fence *fence = NULL; int r; + memset(&vm_update_params, 0, sizeof(vm_update_params)); ring = container_of(vm->entity.sched, struct amdgpu_ring, sched); /* padding, etc. */ @@ -555,7 +569,7 @@ int amdgpu_vm_update_page_directory(struct amdgpu_device *adev, if (r) return r; - ib = &job->ibs[0]; + vm_update_params.ib = &job->ibs[0]; /* walk over the address space and update the page directory */ for (pt_idx = 0; pt_idx <= vm->max_pde_used; ++pt_idx) { @@ -575,7 +589,7 @@ int amdgpu_vm_update_page_directory(struct amdgpu_device *adev, ((last_pt + incr * count) != pt)) { if (count) { - amdgpu_vm_update_pages(adev, 0, NULL, ib, + amdgpu_vm_update_pages(adev, &vm_update_params, last_pde, last_pt, count, incr, AMDGPU_PTE_VALID); @@ -590,14 +604,15 @@ int amdgpu_vm_update_page_directory(struct amdgpu_device *adev, } if (count) - amdgpu_vm_update_pages(adev, 0, NULL, ib, last_pde, last_pt, - count, incr, AMDGPU_PTE_VALID); + amdgpu_vm_update_pages(adev, &vm_update_params, + last_pde, last_pt, + count, incr, AMDGPU_PTE_VALID); - if (ib->length_dw != 0) { - amdgpu_ring_pad_ib(ring, ib); + if (vm_update_params.ib->length_dw != 0) { + amdgpu_ring_pad_ib(ring, vm_update_params.ib); amdgpu_sync_resv(adev, &job->sync, pd->tbo.resv, AMDGPU_FENCE_OWNER_VM); - WARN_ON(ib->length_dw > ndw); + WARN_ON(vm_update_params.ib->length_dw > ndw); r = amdgpu_job_submit(job, ring, &vm->entity, AMDGPU_FENCE_OWNER_VM, &fence); if (r) @@ -623,18 +638,15 @@ int amdgpu_vm_update_page_directory(struct amdgpu_device *adev, * amdgpu_vm_frag_ptes - add fragment information to PTEs * * @adev: amdgpu_device pointer - * @src: address where to copy page table entries from - * @pages_addr: DMA addresses to use for mapping - * @ib: IB for the update + * @vm_update_params: see amdgpu_vm_update_params definition * @pe_start: first PTE to handle * @pe_end: last PTE to handle * @addr: addr those PTEs should point to * @flags: hw mapping flags */ static void amdgpu_vm_frag_ptes(struct amdgpu_device *adev, - uint64_t src, - dma_addr_t *pages_addr, - struct amdgpu_ib *ib, + struct amdgpu_vm_update_params + *vm_update_params, uint64_t pe_start, uint64_t pe_end, uint64_t addr, uint32_t flags) { @@ -671,11 +683,11 @@ static void amdgpu_vm_frag_ptes(struct amdgpu_device *adev, return; /* system pages are non continuously */ - if (src || pages_addr || !(flags & AMDGPU_PTE_VALID) || - (frag_start >= frag_end)) { + if (vm_update_params->src || vm_update_params->pages_addr || + !(flags & AMDGPU_PTE_VALID) || (frag_start >= frag_end)) { count = (pe_end - pe_start) / 8; - amdgpu_vm_update_pages(adev, src, pages_addr, ib, pe_start, + amdgpu_vm_update_pages(adev, vm_update_params, pe_start, addr, count, AMDGPU_GPU_PAGE_SIZE, flags); return; @@ -684,21 +696,21 @@ static void amdgpu_vm_frag_ptes(struct amdgpu_device *adev, /* handle the 4K area at the beginning */ if (pe_start != frag_start) { count = (frag_start - pe_start) / 8; - amdgpu_vm_update_pages(adev, 0, NULL, ib, pe_start, addr, + amdgpu_vm_update_pages(adev, vm_update_params, pe_start, addr, count, AMDGPU_GPU_PAGE_SIZE, flags); addr += AMDGPU_GPU_PAGE_SIZE * count; } /* handle the area in the middle */ count = (frag_end - frag_start) / 8; - amdgpu_vm_update_pages(adev, 0, NULL, ib, frag_start, addr, count, + amdgpu_vm_update_pages(adev, vm_update_params, frag_start, addr, count, AMDGPU_GPU_PAGE_SIZE, flags | frag_flags); /* handle the 4K area at the end */ if (frag_end != pe_end) { addr += AMDGPU_GPU_PAGE_SIZE * count; count = (pe_end - frag_end) / 8; - amdgpu_vm_update_pages(adev, 0, NULL, ib, frag_end, addr, + amdgpu_vm_update_pages(adev, vm_update_params, frag_end, addr, count, AMDGPU_GPU_PAGE_SIZE, flags); } } @@ -707,8 +719,7 @@ static void amdgpu_vm_frag_ptes(struct amdgpu_device *adev, * amdgpu_vm_update_ptes - make sure that page tables are valid * * @adev: amdgpu_device pointer - * @src: address where to copy page table entries from - * @pages_addr: DMA addresses to use for mapping + * @vm_update_params: see amdgpu_vm_update_params definition * @vm: requested vm * @start: start of GPU address range * @end: end of GPU address range @@ -718,10 +729,9 @@ static void amdgpu_vm_frag_ptes(struct amdgpu_device *adev, * Update the page tables in the range @start - @end. */ static void amdgpu_vm_update_ptes(struct amdgpu_device *adev, - uint64_t src, - dma_addr_t *pages_addr, + struct amdgpu_vm_update_params + *vm_update_params, struct amdgpu_vm *vm, - struct amdgpu_ib *ib, uint64_t start, uint64_t end, uint64_t dst, uint32_t flags) { @@ -747,7 +757,7 @@ static void amdgpu_vm_update_ptes(struct amdgpu_device *adev, if (last_pe_end != pe_start) { - amdgpu_vm_frag_ptes(adev, src, pages_addr, ib, + amdgpu_vm_frag_ptes(adev, vm_update_params, last_pe_start, last_pe_end, last_dst, flags); @@ -762,7 +772,7 @@ static void amdgpu_vm_update_ptes(struct amdgpu_device *adev, dst += nptes * AMDGPU_GPU_PAGE_SIZE; } - amdgpu_vm_frag_ptes(adev, src, pages_addr, ib, last_pe_start, + amdgpu_vm_frag_ptes(adev, vm_update_params, last_pe_start, last_pe_end, last_dst, flags); } @@ -794,11 +804,14 @@ static int amdgpu_vm_bo_update_mapping(struct amdgpu_device *adev, void *owner = AMDGPU_FENCE_OWNER_VM; unsigned nptes, ncmds, ndw; struct amdgpu_job *job; - struct amdgpu_ib *ib; + struct amdgpu_vm_update_params vm_update_params; struct fence *f = NULL; int r; ring = container_of(vm->entity.sched, struct amdgpu_ring, sched); + memset(&vm_update_params, 0, sizeof(vm_update_params)); + vm_update_params.src = src; + vm_update_params.pages_addr = pages_addr; /* sync to everything on unmapping */ if (!(flags & AMDGPU_PTE_VALID)) @@ -815,11 +828,11 @@ static int amdgpu_vm_bo_update_mapping(struct amdgpu_device *adev, /* padding, etc. */ ndw = 64; - if (src) { + if (vm_update_params.src) { /* only copy commands needed */ ndw += ncmds * 7; - } else if (pages_addr) { + } else if (vm_update_params.pages_addr) { /* header for write data commands */ ndw += ncmds * 4; @@ -838,7 +851,7 @@ static int amdgpu_vm_bo_update_mapping(struct amdgpu_device *adev, if (r) return r; - ib = &job->ibs[0]; + vm_update_params.ib = &job->ibs[0]; r = amdgpu_sync_resv(adev, &job->sync, vm->page_directory->tbo.resv, owner); @@ -849,11 +862,11 @@ static int amdgpu_vm_bo_update_mapping(struct amdgpu_device *adev, if (r) goto error_free; - amdgpu_vm_update_ptes(adev, src, pages_addr, vm, ib, start, + amdgpu_vm_update_ptes(adev, &vm_update_params, vm, start, last + 1, addr, flags); - amdgpu_ring_pad_ib(ring, ib); - WARN_ON(ib->length_dw > ndw); + amdgpu_ring_pad_ib(ring, vm_update_params.ib); + WARN_ON(vm_update_params.ib->length_dw > ndw); r = amdgpu_job_submit(job, ring, &vm->entity, AMDGPU_FENCE_OWNER_VM, &f); if (r) From bff3c624dc7261a084a4d25a0b09c3fb0fec872a Mon Sep 17 00:00:00 2001 From: Gene Chen Date: Tue, 16 Feb 2016 08:25:14 +0100 Subject: [PATCH 114/393] platform/chrome: chromeos_laptop: Add Leon Touch Add support for Leon touch devices, which is the same as slippy/falco/peppy/wolf on the same buses using the LynxPoint-LP I2C via the i2c-designware-pci driver. Based on the following patch: https://chromium-review.googlesource.com/#/c/168351/ Signed-off-by: Gene Chen Reviewed-by: Benson Leung Signed-off-by: Enric Balletbo i Serra Signed-off-by: Olof Johansson --- drivers/platform/chrome/chromeos_laptop.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index 2b441e9ae593..f5aa0a361412 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -494,6 +494,13 @@ static struct chromeos_laptop cr48 = { }, }; +static struct chromeos_laptop leon = { + .i2c_peripherals = { + /* Touchpad. */ + { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 }, + }, +}; + #define _CBDD(board_) \ .callback = chromeos_laptop_dmi_matched, \ .driver_data = (void *)&board_ @@ -581,6 +588,14 @@ static struct dmi_system_id chromeos_laptop_dmi_table[] __initdata = { }, _CBDD(cr48), }, + { + .ident = "Leon", + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), + DMI_MATCH(DMI_PRODUCT_NAME, "Leon"), + }, + _CBDD(leon), + }, { } }; MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table); From 59a356d2f1e6e2fbb700c9a9cda1658f5934c7d2 Mon Sep 17 00:00:00 2001 From: Aaron Durbin Date: Tue, 16 Feb 2016 08:25:15 +0100 Subject: [PATCH 115/393] platform/chrome: pstore: probe for ramoops buffer using acpi In order to handle the firmware placing the ramoops buffer in a different location than the kernel is configured to look probe for an ACPI device specified by GOOG9999 acpi id. If no device is found or the first memory resource is not defined properly fall back to the configured base and length. Signed-off-by: Aaron Durbin Signed-off-by: Ben Zhang Signed-off-by: Filipe Brandenburger Signed-off-by: Enric Balletbo i Serra Reviewed-by: Olof Johansson Signed-off-by: Olof Johansson --- drivers/platform/chrome/chromeos_pstore.c | 53 ++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/chromeos_pstore.c b/drivers/platform/chrome/chromeos_pstore.c index 34749200e4ab..ca001926ad40 100644 --- a/drivers/platform/chrome/chromeos_pstore.c +++ b/drivers/platform/chrome/chromeos_pstore.c @@ -8,6 +8,7 @@ * the Free Software Foundation, version 2 of the License. */ +#include #include #include #include @@ -71,9 +72,59 @@ static struct platform_device chromeos_ramoops = { }, }; +#ifdef CONFIG_ACPI +static const struct acpi_device_id cros_ramoops_acpi_match[] = { + { "GOOG9999", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_ramoops_acpi_match); + +static struct platform_driver chromeos_ramoops_acpi = { + .driver = { + .name = "chromeos_pstore", + .acpi_match_table = ACPI_PTR(cros_ramoops_acpi_match), + }, +}; + +static int __init chromeos_probe_acpi(struct platform_device *pdev) +{ + struct resource *res; + resource_size_t len; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOMEM; + + len = resource_size(res); + if (!res->start || !len) + return -ENOMEM; + + pr_info("chromeos ramoops using acpi device.\n"); + + chromeos_ramoops_data.mem_size = len; + chromeos_ramoops_data.mem_address = res->start; + + return 0; +} + +static bool __init chromeos_check_acpi(void) +{ + if (!platform_driver_probe(&chromeos_ramoops_acpi, chromeos_probe_acpi)) + return true; + return false; +} +#else +static inline bool chromeos_check_acpi(void) { return false; } +#endif + static int __init chromeos_pstore_init(void) { - if (dmi_check_system(chromeos_pstore_dmi_table)) + bool acpi_dev_found; + + /* First check ACPI for non-hardcoded values from firmware. */ + acpi_dev_found = chromeos_check_acpi(); + + if (acpi_dev_found || dmi_check_system(chromeos_pstore_dmi_table)) return platform_device_register(&chromeos_ramoops); return -ENODEV; From f929efb06578d2f9afd869911a8b4cc21b2e6e73 Mon Sep 17 00:00:00 2001 From: Olof Johansson Date: Tue, 16 Feb 2016 08:25:16 +0100 Subject: [PATCH 116/393] platform/chrome: pstore: Move to larger record size. Accidentally specified a smaller record size, bring it back to the same size as we had when we used the config file. Signed-off-by: Olof Johansson Signed-off-by: Enric Balletbo i Serra Reviewed-by: Sameer Nanda Signed-off-by: Olof Johansson --- drivers/platform/chrome/chromeos_pstore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/chromeos_pstore.c b/drivers/platform/chrome/chromeos_pstore.c index ca001926ad40..308a853ac4f1 100644 --- a/drivers/platform/chrome/chromeos_pstore.c +++ b/drivers/platform/chrome/chromeos_pstore.c @@ -59,7 +59,7 @@ MODULE_DEVICE_TABLE(dmi, chromeos_pstore_dmi_table); static struct ramoops_platform_data chromeos_ramoops_data = { .mem_size = 0x100000, .mem_address = 0xf00000, - .record_size = 0x20000, + .record_size = 0x40000, .console_size = 0x20000, .ftrace_size = 0x20000, .dump_oops = 1, From 48b9b6d4699e23cda45756b6a1fe5d481ba45914 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Sun, 27 Dec 2015 21:15:45 +0800 Subject: [PATCH 117/393] platform/chrome: use to_platform_device() Use to_platform_device() instead of open-coding it. Signed-off-by: Geliang Tang Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_lightbar.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index ff7640575c75..a79fb86e1fb9 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -412,8 +412,7 @@ static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, struct device *dev = container_of(kobj, struct device, kobj); struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); - struct platform_device *pdev = container_of(ec->dev, - struct platform_device, dev); + struct platform_device *pdev = to_platform_device(ec->dev); if (pdev->id != 0) return 0; From 492ef7829d2d09428803bffb187d5781bbc12ca5 Mon Sep 17 00:00:00 2001 From: Simon Que Date: Tue, 8 Mar 2016 11:12:46 -0800 Subject: [PATCH 118/393] platform/chrome: Add Chrome OS keyboard backlight LEDs support This is a driver for ACPI-based keyboard backlight LEDs found on Chromebooks. The driver locates \\_SB.KBLT ACPI device and exports backlight as "chromeos::kbd_backlight" LED class device in sysfs. Signed-off-by: Simon Que Signed-off-by: Duncan Laurie Tested-by: Evan McClain Signed-off-by: Dmitry Torokhov Signed-off-by: Olof Johansson --- drivers/platform/chrome/Kconfig | 10 ++ drivers/platform/chrome/Makefile | 15 ++- .../platform/chrome/cros_kbd_led_backlight.c | 122 ++++++++++++++++++ 3 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 drivers/platform/chrome/cros_kbd_led_backlight.c diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index d03df4a60d05..76bdae1a93bb 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -64,4 +64,14 @@ config CROS_EC_PROTO help ChromeOS EC communication protocol helpers. +config CROS_KBD_LED_BACKLIGHT + tristate "Backlight LED support for Chrome OS keyboards" + depends on LEDS_CLASS && ACPI + help + This option enables support for the keyboard backlight LEDs on + select Chrome OS systems. + + To compile this driver as a module, choose M here: the + module will be called cros_kbd_led_backlight. + endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index bc498bda8211..4f3462783a3c 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,8 +1,9 @@ -obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o -obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o -cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o \ - cros_ec_lightbar.o cros_ec_vbc.o -obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o -obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o -obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o +obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o +obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o +cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o \ + cros_ec_lightbar.o cros_ec_vbc.o +obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o +obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o +obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o +obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o diff --git a/drivers/platform/chrome/cros_kbd_led_backlight.c b/drivers/platform/chrome/cros_kbd_led_backlight.c new file mode 100644 index 000000000000..ca3e4da852b4 --- /dev/null +++ b/drivers/platform/chrome/cros_kbd_led_backlight.c @@ -0,0 +1,122 @@ +/* + * Keyboard backlight LED driver for Chrome OS. + * + * Copyright (C) 2012 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Keyboard LED ACPI Device must be defined in firmware */ +#define ACPI_KEYBOARD_BACKLIGHT_DEVICE "\\_SB.KBLT" +#define ACPI_KEYBOARD_BACKLIGHT_READ ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBQC" +#define ACPI_KEYBOARD_BACKLIGHT_WRITE ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBCM" + +#define ACPI_KEYBOARD_BACKLIGHT_MAX 100 + +static void keyboard_led_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + union acpi_object param; + struct acpi_object_list input; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = brightness; + input.count = 1; + input.pointer = ¶m; + + status = acpi_evaluate_object(NULL, ACPI_KEYBOARD_BACKLIGHT_WRITE, + &input, NULL); + if (ACPI_FAILURE(status)) + dev_err(cdev->dev, "Error setting keyboard LED value: %d\n", + status); +} + +static enum led_brightness +keyboard_led_get_brightness(struct led_classdev *cdev) +{ + unsigned long long brightness; + acpi_status status; + + status = acpi_evaluate_integer(NULL, ACPI_KEYBOARD_BACKLIGHT_READ, + NULL, &brightness); + if (ACPI_FAILURE(status)) { + dev_err(cdev->dev, "Error getting keyboard LED value: %d\n", + status); + return -EIO; + } + + return brightness; +} + +static int keyboard_led_probe(struct platform_device *pdev) +{ + struct led_classdev *cdev; + acpi_handle handle; + acpi_status status; + int error; + + /* Look for the keyboard LED ACPI Device */ + status = acpi_get_handle(ACPI_ROOT_OBJECT, + ACPI_KEYBOARD_BACKLIGHT_DEVICE, + &handle); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "Unable to find ACPI device %s: %d\n", + ACPI_KEYBOARD_BACKLIGHT_DEVICE, status); + return -ENXIO; + } + + cdev = devm_kzalloc(&pdev->dev, sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + cdev->name = "chromeos::kbd_backlight"; + cdev->max_brightness = ACPI_KEYBOARD_BACKLIGHT_MAX; + cdev->flags |= LED_CORE_SUSPENDRESUME; + cdev->brightness_set = keyboard_led_set_brightness; + cdev->brightness_get = keyboard_led_get_brightness; + + error = devm_led_classdev_register(&pdev->dev, cdev); + if (error) + return error; + + return 0; +} + +static const struct acpi_device_id keyboard_led_id[] = { + { "GOOG0002", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, keyboard_led_id); + +static struct platform_driver keyboard_led_driver = { + .driver = { + .name = "chromeos-keyboard-leds", + .acpi_match_table = ACPI_PTR(keyboard_led_id), + }, + .probe = keyboard_led_probe, +}; +module_platform_driver(keyboard_led_driver); + +MODULE_AUTHOR("Simon Que "); +MODULE_DESCRIPTION("ChromeOS Keyboard backlight LED Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:chromeos-keyboard-leds"); From 5d749d0bbe811c10d9048cde6dfebc761713abfd Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Tue, 8 Mar 2016 09:13:52 -0800 Subject: [PATCH 119/393] platform/chrome: cros_ec_dev - Fix security issue Prevent memory scribble by checking that ioctl buffer size parameters are sane. Without this check, on 32 bits system, if .insize = 0xffffffff - 20 and .outsize the amount to scribble, we would overflow, allocate a small amounts and be able to write outside of the malloc'ed area. Adding a hard limit allows argument checking of the ioctl. With the current EC, it is expected .insize and .outsize to be at around 512 bytes or less. Signed-off-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_dev.c | 4 ++++ drivers/platform/chrome/cros_ec_proto.c | 4 ++-- include/linux/mfd/cros_ec.h | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index d45cd254ed1c..187470c8a1f6 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -137,6 +137,10 @@ static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg) if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) return -EFAULT; + if ((u_cmd.outsize > EC_MAX_MSG_BYTES) || + (u_cmd.insize > EC_MAX_MSG_BYTES)) + return -EINVAL; + s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize), GFP_KERNEL); if (!s_cmd) diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 990308ca384f..b6e161f71b26 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -298,8 +298,8 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev) ec_dev->max_response = EC_PROTO2_MAX_PARAM_SIZE; ec_dev->max_passthru = 0; ec_dev->pkt_xfer = NULL; - ec_dev->din_size = EC_MSG_BYTES; - ec_dev->dout_size = EC_MSG_BYTES; + ec_dev->din_size = EC_PROTO2_MSG_BYTES; + ec_dev->dout_size = EC_PROTO2_MSG_BYTES; } else { /* * It's possible for a test to occur too early when diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index a677c2bd485c..64184d27e3cd 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -50,9 +50,11 @@ enum { EC_MSG_TX_TRAILER_BYTES, EC_MSG_RX_PROTO_BYTES = 3, - /* Max length of messages */ - EC_MSG_BYTES = EC_PROTO2_MAX_PARAM_SIZE + + /* Max length of messages for proto 2*/ + EC_PROTO2_MSG_BYTES = EC_PROTO2_MAX_PARAM_SIZE + EC_MSG_TX_PROTO_BYTES, + + EC_MAX_MSG_BYTES = 64 * 1024, }; /* From d940f3065c120af233d36933e94ebb577a695b44 Mon Sep 17 00:00:00 2001 From: Clinton Sprain Date: Wed, 11 May 2016 11:05:35 -0700 Subject: [PATCH 120/393] platform/chrome: cros_ec_lightbar - use name instead of ID to hide lightbar attributes Lightbar attributes are hidden if the ID of the device is not 0 (the assumption being that 0 = cros_ec = might have a lightbar, 1 = cros_pd = hide); however, sometimes these devices get IDs 1 and 2 (or something else) instead of IDs 0 and 1. This prevents the lightbar attributes from appearing when they should. Proposed change is to instead check whether the name assigned to the device is CROS_EC_DEV_NAME (true for cros_ec, false for cros_pd). Signed-off-by: Clinton Sprain Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_lightbar.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index a79fb86e1fb9..8df3d447cacf 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -413,7 +413,12 @@ static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); struct platform_device *pdev = to_platform_device(ec->dev); - if (pdev->id != 0) + struct cros_ec_platform *pdata = pdev->dev.platform_data; + int is_cros_ec; + + is_cros_ec = strcmp(pdata->ec_name, CROS_EC_DEV_NAME); + + if (is_cros_ec != 0) return 0; /* Only instantiate this stuff if the EC has a lightbar */ From 2521ea3e0d1b285cea371a38772d3eefa0490c71 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 14 Apr 2016 19:35:29 -0700 Subject: [PATCH 121/393] platform/chrome: cros_ec_dev - Populate compat_ioctl compat_ioctl has to be populated for 32 bit userspace applications to work with 64 bit kernels. Signed-off-by: Guenter Roeck Tested-by: Brian Norris Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_dev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 187470c8a1f6..6d8ee3b15872 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -212,6 +212,9 @@ static const struct file_operations fops = { .release = ec_device_release, .read = ec_device_read, .unlocked_ioctl = ec_device_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ec_device_ioctl, +#endif }; static void __remove(struct device *dev) From 9bd9a90b013c647ed88ed4fa69b664b770924cf0 Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Mon, 2 May 2016 08:57:16 +0800 Subject: [PATCH 122/393] platform/chrome: chromeos_laptop - Add elan trackpad option for C720 Add the elan trackpad to the Acer C720 (peppy) list, as it is an alternate trackpad option. It may exist at i2c address 0x15. Based on this change from the chromeos kernel : https://chromium-review.googlesource.com/186253 Signed-off-by: Benson Leung Signed-off-by: Olof Johansson --- drivers/platform/chrome/chromeos_laptop.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index f5aa0a361412..6ea4f1ad382a 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -34,6 +34,7 @@ #define ATMEL_TS_I2C_ADDR 0x4a #define ATMEL_TS_I2C_BL_ADDR 0x26 #define CYAPA_TP_I2C_ADDR 0x67 +#define ELAN_TP_I2C_ADDR 0x15 #define ISL_ALS_I2C_ADDR 0x44 #define TAOS_ALS_I2C_ADDR 0x29 @@ -73,7 +74,7 @@ struct i2c_peripheral { int tries; }; -#define MAX_I2C_PERIPHERALS 3 +#define MAX_I2C_PERIPHERALS 4 struct chromeos_laptop { struct i2c_peripheral i2c_peripherals[MAX_I2C_PERIPHERALS]; @@ -86,6 +87,11 @@ static struct i2c_board_info cyapa_device = { .flags = I2C_CLIENT_WAKE, }; +static struct i2c_board_info elantech_device = { + I2C_BOARD_INFO("elan_i2c", ELAN_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, +}; + static struct i2c_board_info isl_als_device = { I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR), }; @@ -306,6 +312,16 @@ static int setup_atmel_224s_tp(enum i2c_adapter_type type) return (!tp) ? -EAGAIN : 0; } +static int setup_elantech_tp(enum i2c_adapter_type type) +{ + if (tp) + return 0; + + /* add elantech touchpad */ + tp = add_i2c_device("trackpad", type, &elantech_device); + return (!tp) ? -EAGAIN : 0; +} + static int setup_atmel_1664s_ts(enum i2c_adapter_type type) { const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR, @@ -475,6 +491,8 @@ static struct chromeos_laptop acer_c720 = { { .add = setup_atmel_1664s_ts, I2C_ADAPTER_DESIGNWARE_1 }, /* Touchpad. */ { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 }, + /* Elan Touchpad option. */ + { .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 }, /* Light Sensor. */ { .add = setup_isl29018_als, I2C_ADAPTER_DESIGNWARE_1 }, }, From 9e96aa70e9acd03eee61c2094b4755b80a386c47 Mon Sep 17 00:00:00 2001 From: Charlie Mooney Date: Mon, 2 May 2016 08:57:17 +0800 Subject: [PATCH 123/393] platform/chrome: chromeos_laptop - Add Elan touchpad for Wolf The upcoming Elan Wolf (Dell Chromebook 11) devices need to know to look for Elan touchpads on the i2c bus so that they will be functional. Based on the chromeos-kernel commit : https://chromium-review.googlesource.com/198283 Signed-off-by: Charlie Mooney Signed-off-by: Benson Leung Signed-off-by: Olof Johansson --- drivers/platform/chrome/chromeos_laptop.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index 6ea4f1ad382a..8398a7d96490 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -461,6 +461,8 @@ static struct chromeos_laptop dell_chromebook_11 = { .i2c_peripherals = { /* Touchpad. */ { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 }, + /* Elan Touchpad option. */ + { .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 }, }, }; From de1965159a34951a86267d13db4f2a67234139d3 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Thu, 12 May 2016 01:36:40 +0300 Subject: [PATCH 124/393] rcar: src: skip disabled-SRC nodes The current device tree representation of the R-Car Sample Rate Converters (SRC) assumes that they are numbered consecutively, starting from 0. Alas, this is not the case with the R8A7794 SoC where SRC0 isn't present. In order to keep the existing device trees working, I'm suggesting to use a disabled node for SRC0. Teach the SRC probe to just skip disabled nodes. Signed-off-by: Sergei Shtylyov Acked-by: Kuninori Morimoto Signed-off-by: Mark Brown --- sound/soc/sh/rcar/src.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 15d6ffe8be74..e39f916d0f2f 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -572,6 +572,9 @@ int rsnd_src_probe(struct rsnd_priv *priv) i = 0; for_each_child_of_node(node, np) { + if (!of_device_is_available(np)) + goto skip; + src = rsnd_src_get(priv, i); snprintf(name, RSND_SRC_NAME_SIZE, "%s.%d", @@ -595,6 +598,7 @@ int rsnd_src_probe(struct rsnd_priv *priv) if (ret) goto rsnd_src_probe_done; +skip: i++; } From 2213fc350841e99598e52232b56add7b873529a0 Mon Sep 17 00:00:00 2001 From: Jeremy McDermond Date: Wed, 11 May 2016 12:09:53 -0700 Subject: [PATCH 125/393] ASoC: tlv320aic32x4: Properly implement the positive and negative pins into the mixers The TLV320AIC32x4 has a very flexible mixer on the inputs to the ADCs. Each mixer has an available set of available pins that can be connected to the ADC positive and negative pins via three different resistor values. This allows for configuration of differential inputs as well as doing level manipulation between sources going into the mixers. The current code only provides positive pins and I implemented the resistors in an earlier patch. It turns out that it appears to more accurately model what's happening to implement each of the pins as a MUX rather than on/off switches and a mixer. This way each pin can be set to its desired resistor value. Since there are no switches, the mixer is no longer necessary in the DAPM path. I set the DAPM paths such that the "off" position of any of the MUXes turns the path off. This should allow for any input confiuration available on the codec. Signed-off-by: Jeremy McDermond Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 204 ++++++++++++++++++++++++------- 1 file changed, 158 insertions(+), 46 deletions(-) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 0eb8acc8cd66..85d4978d0384 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -187,36 +187,67 @@ static const char * const resistor_text[] = { "Off", "10 kOhm", "20 kOhm", "40 kOhm", }; -static SOC_ENUM_SINGLE_DECL(in1l_lpga_p_enum, AIC32X4_LMICPGAPIN, 6, - resistor_text); -static SOC_ENUM_SINGLE_DECL(in2l_lpga_p_enum, AIC32X4_LMICPGAPIN, 4, - resistor_text); -static SOC_ENUM_SINGLE_DECL(in3l_lpga_p_enum, AIC32X4_LMICPGAPIN, 2, - resistor_text); -static SOC_ENUM_SINGLE_DECL(in1r_lpga_p_enum, AIC32X4_LMICPGAPIN, 0, - resistor_text); +/* Left mixer pins */ +static SOC_ENUM_SINGLE_DECL(in1l_lpga_p_enum, AIC32X4_LMICPGAPIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_lpga_p_enum, AIC32X4_LMICPGAPIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3l_lpga_p_enum, AIC32X4_LMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in1r_lpga_p_enum, AIC32X4_LMICPGAPIN, 0, resistor_text); -static const struct snd_kcontrol_new left_input_mixer_controls[] = { - SOC_DAPM_ENUM("IN1_L P Switch", in1l_lpga_p_enum), - SOC_DAPM_ENUM("IN2_L P Switch", in2l_lpga_p_enum), - SOC_DAPM_ENUM("IN3_L P Switch", in3l_lpga_p_enum), - SOC_DAPM_ENUM("IN1_R P Switch", in1r_lpga_p_enum), +static SOC_ENUM_SINGLE_DECL(cml_lpga_n_enum, AIC32X4_LMICPGANIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2r_lpga_n_enum, AIC32X4_LMICPGANIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_text); + +static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), +}; +static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), +}; +static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), +}; +static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), }; -static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, - resistor_text); -static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, - resistor_text); -static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, - resistor_text); -static SOC_ENUM_SINGLE_DECL(in2l_rpga_p_enum, AIC32X4_RMICPGAPIN, 0, - resistor_text); +/* Right mixer pins */ +static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_rpga_p_enum, AIC32X4_RMICPGAPIN, 0, resistor_text); +static SOC_ENUM_SINGLE_DECL(cmr_rpga_n_enum, AIC32X4_RMICPGANIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in1l_rpga_n_enum, AIC32X4_RMICPGANIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_text); -static const struct snd_kcontrol_new right_input_mixer_controls[] = { - SOC_DAPM_ENUM("IN1_R P Switch", in1r_rpga_p_enum), - SOC_DAPM_ENUM("IN2_R P Switch", in2r_rpga_p_enum), - SOC_DAPM_ENUM("IN3_R P Switch", in3r_rpga_p_enum), - SOC_DAPM_ENUM("IN2_L P Switch", in2l_rpga_p_enum), +static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), +}; +static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), +}; +static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), +}; +static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), }; static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { @@ -240,14 +271,39 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { &lor_output_mixer_controls[0], ARRAY_SIZE(lor_output_mixer_controls)), SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0, - &left_input_mixer_controls[0], - ARRAY_SIZE(left_input_mixer_controls)), - SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0, - &right_input_mixer_controls[0], - ARRAY_SIZE(right_input_mixer_controls)), - SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in3r_to_lmixer_controls), + SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0), SND_SOC_DAPM_OUTPUT("HPL"), @@ -287,21 +343,77 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { {"LOR Power", NULL, "LOR Output Mixer"}, {"LOR", NULL, "LOR Power"}, - /* Left input */ - {"Left Input Mixer", "IN1_L P Switch", "IN1_L"}, - {"Left Input Mixer", "IN2_L P Switch", "IN2_L"}, - {"Left Input Mixer", "IN3_L P Switch", "IN3_L"}, - {"Left Input Mixer", "IN1_R P Switch", "IN1_R"}, - - {"Left ADC", NULL, "Left Input Mixer"}, - /* Right Input */ - {"Right Input Mixer", "IN1_R P Switch", "IN1_R"}, - {"Right Input Mixer", "IN2_R P Switch", "IN2_R"}, - {"Right Input Mixer", "IN3_R P Switch", "IN3_R"}, - {"Right Input Mixer", "IN2_L P Switch", "IN2_L"}, + {"Right ADC", NULL, "IN1_R to Right Mixer Positive Resistor"}, + {"IN1_R to Right Mixer Positive Resistor", "10 kOhm", "IN1_R"}, + {"IN1_R to Right Mixer Positive Resistor", "20 kOhm", "IN1_R"}, + {"IN1_R to Right Mixer Positive Resistor", "40 kOhm", "IN1_R"}, - {"Right ADC", NULL, "Right Input Mixer"}, + {"Right ADC", NULL, "IN2_R to Right Mixer Positive Resistor"}, + {"IN2_R to Right Mixer Positive Resistor", "10 kOhm", "IN2_R"}, + {"IN2_R to Right Mixer Positive Resistor", "20 kOhm", "IN2_R"}, + {"IN2_R to Right Mixer Positive Resistor", "40 kOhm", "IN2_R"}, + + {"Right ADC", NULL, "IN3_R to Right Mixer Positive Resistor"}, + {"IN3_R to Right Mixer Positive Resistor", "10 kOhm", "IN3_R"}, + {"IN3_R to Right Mixer Positive Resistor", "20 kOhm", "IN3_R"}, + {"IN3_R to Right Mixer Positive Resistor", "40 kOhm", "IN3_R"}, + + {"Right ADC", NULL, "IN2_L to Right Mixer Positive Resistor"}, + {"IN2_L to Right Mixer Positive Resistor", "10 kOhm", "IN2_L"}, + {"IN2_L to Right Mixer Positive Resistor", "20 kOhm", "IN2_L"}, + {"IN2_L to Right Mixer Positive Resistor", "40 kOhm", "IN2_L"}, + + {"Right ADC", NULL, "CM_R to Right Mixer Negative Resistor"}, + {"CM_R to Right Mixer Negative Resistor", "10 kOhm", "CM_R"}, + {"CM_R to Right Mixer Negative Resistor", "20 kOhm", "CM_R"}, + {"CM_R to Right Mixer Negative Resistor", "40 kOhm", "CM_R"}, + + {"Right ADC", NULL, "IN1_L to Right Mixer Negative Resistor"}, + {"IN1_L to Right Mixer Negative Resistor", "10 kOhm", "IN1_L"}, + {"IN1_L to Right Mixer Negative Resistor", "20 kOhm", "IN1_L"}, + {"IN1_L to Right Mixer Negative Resistor", "40 kOhm", "IN1_L"}, + + {"Right ADC", NULL, "IN3_L to Right Mixer Negative Resistor"}, + {"IN3_L to Right Mixer Negative Resistor", "10 kOhm", "IN3_L"}, + {"IN3_L to Right Mixer Negative Resistor", "20 kOhm", "IN3_L"}, + {"IN3_L to Right Mixer Negative Resistor", "40 kOhm", "IN3_L"}, + + /* Left Input */ + {"Left ADC", NULL, "IN1_L to Left Mixer Positive Resistor"}, + {"IN1_L to Left Mixer Positive Resistor", "10 kOhm", "IN1_L"}, + {"IN1_L to Left Mixer Positive Resistor", "20 kOhm", "IN1_L"}, + {"IN1_L to Left Mixer Positive Resistor", "40 kOhm", "IN1_L"}, + + {"Left ADC", NULL, "IN2_L to Left Mixer Positive Resistor"}, + {"IN2_L to Left Mixer Positive Resistor", "10 kOhm", "IN2_L"}, + {"IN2_L to Left Mixer Positive Resistor", "20 kOhm", "IN2_L"}, + {"IN2_L to Left Mixer Positive Resistor", "40 kOhm", "IN2_L"}, + + {"Left ADC", NULL, "IN3_L to Left Mixer Positive Resistor"}, + {"IN3_L to Left Mixer Positive Resistor", "10 kOhm", "IN3_L"}, + {"IN3_L to Left Mixer Positive Resistor", "20 kOhm", "IN3_L"}, + {"IN3_L to Left Mixer Positive Resistor", "40 kOhm", "IN3_L"}, + + {"Left ADC", NULL, "IN1_R to Left Mixer Positive Resistor"}, + {"IN1_R to Left Mixer Positive Resistor", "10 kOhm", "IN1_R"}, + {"IN1_R to Left Mixer Positive Resistor", "20 kOhm", "IN1_R"}, + {"IN1_R to Left Mixer Positive Resistor", "40 kOhm", "IN1_R"}, + + {"Left ADC", NULL, "CM_L to Left Mixer Negative Resistor"}, + {"CM_L to Left Mixer Negative Resistor", "10 kOhm", "CM_L"}, + {"CM_L to Left Mixer Negative Resistor", "20 kOhm", "CM_L"}, + {"CM_L to Left Mixer Negative Resistor", "40 kOhm", "CM_L"}, + + {"Left ADC", NULL, "IN2_R to Left Mixer Negative Resistor"}, + {"IN2_R to Left Mixer Negative Resistor", "10 kOhm", "IN2_R"}, + {"IN2_R to Left Mixer Negative Resistor", "20 kOhm", "IN2_R"}, + {"IN2_R to Left Mixer Negative Resistor", "40 kOhm", "IN2_R"}, + + {"Left ADC", NULL, "IN3_R to Left Mixer Negative Resistor"}, + {"IN3_R to Left Mixer Negative Resistor", "10 kOhm", "IN3_R"}, + {"IN3_R to Left Mixer Negative Resistor", "20 kOhm", "IN3_R"}, + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, }; static const struct regmap_range_cfg aic32x4_regmap_pages[] = { From 9ab9cf058a5de590146fdc67bf9c483c8bdcbffa Mon Sep 17 00:00:00 2001 From: Rex Zhu Date: Tue, 3 May 2016 15:47:15 +0800 Subject: [PATCH 126/393] drm/amd/powerplay: SCLK will have a big drop with low VDDC when PPlib was enabled. SMC uses CurrSclkPllRange structure to keep track of what range of PLL SCLK is sitting on. Driver overwrites this value to 0 because it's part of DPM table and driver doesn't program this. This change will set this field to 0xFF every time there's a init SMC table call. Signed-off-by: Rex Zhu Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c index 93768fa1dcdc..94b2a73f0151 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c @@ -2091,7 +2091,7 @@ static int polaris10_init_smc_table(struct pp_hwmgr *hwmgr) "Failed to populate Clock Stretcher Data Table!", return result); } - + table->CurrSclkPllRange = 0xff; table->GraphicsVoltageChangeEnable = 1; table->GraphicsThermThrottleEnable = 1; table->GraphicsInterval = 1; @@ -2184,6 +2184,7 @@ static int polaris10_init_smc_table(struct pp_hwmgr *hwmgr) CONVERT_FROM_HOST_TO_SMC_UL(table->SmioMask1); CONVERT_FROM_HOST_TO_SMC_UL(table->SmioMask2); CONVERT_FROM_HOST_TO_SMC_UL(table->SclkStepSize); + CONVERT_FROM_HOST_TO_SMC_UL(table->CurrSclkPllRange); CONVERT_FROM_HOST_TO_SMC_US(table->TemperatureLimitHigh); CONVERT_FROM_HOST_TO_SMC_US(table->TemperatureLimitLow); CONVERT_FROM_HOST_TO_SMC_US(table->VoltageResponseTime); From 2dcbffad94f3c2f85cc2c433dad382a741e91dc4 Mon Sep 17 00:00:00 2001 From: Rex Zhu Date: Tue, 10 May 2016 13:39:29 +0800 Subject: [PATCH 127/393] drm/amd/powerplay: fix NULL point check error && was used instead of || Signed-off-by: Rex Zhu Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c index 94b2a73f0151..4115dd8ec051 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c @@ -199,8 +199,9 @@ void phm_apply_dal_min_voltage_request(struct pp_hwmgr *hwmgr) enum PP_DAL_POWERLEVEL dal_power_level = hwmgr->dal_power_level; uint32_t req_vddc = 0, req_volt, i; - if (!table && !(dal_power_level >= PP_DAL_POWERLEVEL_ULTRALOW && - dal_power_level <= PP_DAL_POWERLEVEL_PERFORMANCE)) + if (!table || table-count <= 0 + || dal_power_level < PP_DAL_POWERLEVEL_ULTRALOW + || dal_power_level > PP_DAL_POWERLEVEL_PERFORMANCE) return; for (i = 0; i < table->count; i++) { From 8b41e7a03a306d93276f91c1dae39dc51a1af58d Mon Sep 17 00:00:00 2001 From: Rex Zhu Date: Tue, 10 May 2016 15:37:00 +0800 Subject: [PATCH 128/393] drm/amd/powerplay: move asic unrelated function to hwmgr.c. It's generic and used by multiple asics. Signed-off-by: Rex Zhu Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- .../gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c | 37 +----------------- drivers/gpu/drm/amd/powerplay/hwmgr/hwmgr.c | 38 +++++++++++++++++++ .../drm/amd/powerplay/hwmgr/polaris10_hwmgr.c | 36 ------------------ drivers/gpu/drm/amd/powerplay/inc/hwmgr.h | 2 +- 4 files changed, 40 insertions(+), 73 deletions(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c index c94f9faa220a..382223b2f047 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c @@ -3573,46 +3573,11 @@ static int fiji_force_dpm_highest(struct pp_hwmgr *hwmgr) return 0; } -static void fiji_apply_dal_min_voltage_request(struct pp_hwmgr *hwmgr) -{ - struct phm_ppt_v1_information *table_info = - (struct phm_ppt_v1_information *)hwmgr->pptable; - struct phm_clock_voltage_dependency_table *table = - table_info->vddc_dep_on_dal_pwrl; - struct phm_ppt_v1_clock_voltage_dependency_table *vddc_table; - enum PP_DAL_POWERLEVEL dal_power_level = hwmgr->dal_power_level; - uint32_t req_vddc = 0, req_volt, i; - - if (!table && !(dal_power_level >= PP_DAL_POWERLEVEL_ULTRALOW && - dal_power_level <= PP_DAL_POWERLEVEL_PERFORMANCE)) - return; - - for (i= 0; i < table->count; i++) { - if (dal_power_level == table->entries[i].clk) { - req_vddc = table->entries[i].v; - break; - } - } - - vddc_table = table_info->vdd_dep_on_sclk; - for (i= 0; i < vddc_table->count; i++) { - if (req_vddc <= vddc_table->entries[i].vddc) { - req_volt = (((uint32_t)vddc_table->entries[i].vddc) * VOLTAGE_SCALE) - << VDDC_SHIFT; - smum_send_msg_to_smc_with_parameter(hwmgr->smumgr, - PPSMC_MSG_VddC_Request, req_volt); - return; - } - } - printk(KERN_ERR "DAL requested level can not" - " found a available voltage in VDDC DPM Table \n"); -} - static int fiji_upload_dpmlevel_enable_mask(struct pp_hwmgr *hwmgr) { struct fiji_hwmgr *data = (struct fiji_hwmgr *)(hwmgr->backend); - fiji_apply_dal_min_voltage_request(hwmgr); + phm_apply_dal_min_voltage_request(hwmgr); if (!data->sclk_dpm_key_disabled) { if (data->dpm_level_enable_mask.sclk_dpm_enable_mask) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/hwmgr.c index 7d69ed635bc2..1c48917da3cf 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/hwmgr.c @@ -30,6 +30,9 @@ #include "pppcielanes.h" #include "pp_debug.h" #include "ppatomctrl.h" +#include "ppsmc.h" + +#define VOLTAGE_SCALE 4 extern int cz_hwmgr_init(struct pp_hwmgr *hwmgr); extern int tonga_hwmgr_init(struct pp_hwmgr *hwmgr); @@ -566,3 +569,38 @@ uint32_t phm_get_lowest_enabled_level(struct pp_hwmgr *hwmgr, uint32_t mask) return level; } + +void phm_apply_dal_min_voltage_request(struct pp_hwmgr *hwmgr) +{ + struct phm_ppt_v1_information *table_info = + (struct phm_ppt_v1_information *)hwmgr->pptable; + struct phm_clock_voltage_dependency_table *table = + table_info->vddc_dep_on_dal_pwrl; + struct phm_ppt_v1_clock_voltage_dependency_table *vddc_table; + enum PP_DAL_POWERLEVEL dal_power_level = hwmgr->dal_power_level; + uint32_t req_vddc = 0, req_volt, i; + + if (!table || table->count <= 0 + || dal_power_level < PP_DAL_POWERLEVEL_ULTRALOW + || dal_power_level > PP_DAL_POWERLEVEL_PERFORMANCE) + return; + + for (i = 0; i < table->count; i++) { + if (dal_power_level == table->entries[i].clk) { + req_vddc = table->entries[i].v; + break; + } + } + + vddc_table = table_info->vdd_dep_on_sclk; + for (i = 0; i < vddc_table->count; i++) { + if (req_vddc <= vddc_table->entries[i].vddc) { + req_volt = (((uint32_t)vddc_table->entries[i].vddc) * VOLTAGE_SCALE); + smum_send_msg_to_smc_with_parameter(hwmgr->smumgr, + PPSMC_MSG_VddC_Request, req_volt); + return; + } + } + printk(KERN_ERR "DAL requested level can not" + " found a available voltage in VDDC DPM Table \n"); +} diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c index 4115dd8ec051..4cef7a527696 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c @@ -189,42 +189,6 @@ int phm_get_current_pcie_lane_number(struct pp_hwmgr *hwmgr) return decode_pcie_lane_width(link_width); } -void phm_apply_dal_min_voltage_request(struct pp_hwmgr *hwmgr) -{ - struct phm_ppt_v1_information *table_info = - (struct phm_ppt_v1_information *)hwmgr->pptable; - struct phm_clock_voltage_dependency_table *table = - table_info->vddc_dep_on_dal_pwrl; - struct phm_ppt_v1_clock_voltage_dependency_table *vddc_table; - enum PP_DAL_POWERLEVEL dal_power_level = hwmgr->dal_power_level; - uint32_t req_vddc = 0, req_volt, i; - - if (!table || table-count <= 0 - || dal_power_level < PP_DAL_POWERLEVEL_ULTRALOW - || dal_power_level > PP_DAL_POWERLEVEL_PERFORMANCE) - return; - - for (i = 0; i < table->count; i++) { - if (dal_power_level == table->entries[i].clk) { - req_vddc = table->entries[i].v; - break; - } - } - - vddc_table = table_info->vdd_dep_on_sclk; - for (i = 0; i < vddc_table->count; i++) { - if (req_vddc <= vddc_table->entries[i].vddc) { - req_volt = (((uint32_t)vddc_table->entries[i].vddc) * VOLTAGE_SCALE) - << VDDC_SHIFT; - smum_send_msg_to_smc_with_parameter(hwmgr->smumgr, - PPSMC_MSG_VddC_Request, req_volt); - return; - } - } - printk(KERN_ERR "DAL requested level can not" - " found a available voltage in VDDC DPM Table \n"); -} - /** * Enable voltage control * diff --git a/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h b/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h index fd4ce7aaeee9..28f571449495 100644 --- a/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h +++ b/drivers/gpu/drm/amd/powerplay/inc/hwmgr.h @@ -673,7 +673,7 @@ extern int phm_get_sclk_for_voltage_evv(struct pp_hwmgr *hwmgr, phm_ppt_v1_volta extern int phm_initializa_dynamic_state_adjustment_rule_settings(struct pp_hwmgr *hwmgr); extern int phm_hwmgr_backend_fini(struct pp_hwmgr *hwmgr); extern uint32_t phm_get_lowest_enabled_level(struct pp_hwmgr *hwmgr, uint32_t mask); - +extern void phm_apply_dal_min_voltage_request(struct pp_hwmgr *hwmgr); #define PHM_ENTIRE_REGISTER_MASK 0xFFFFFFFFU From d573de2d00835e38cef1fb4bff7b49c174c68941 Mon Sep 17 00:00:00 2001 From: Rex Zhu Date: Thu, 12 May 2016 13:27:28 +0800 Subject: [PATCH 129/393] drm/amdgpu: create fence slab once when amdgpu module init. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids problems with multiple GPUs. For example, if the first GPU failed before amdgpu_fence_init() was called, amdgpu_fence_slab_ref is still 0 and it will get decremented in amdgpu_fence_driver_fini(). This will lead to a crash during init of the second GPU since amdgpu_fence_slab_ref is not 0. v2: add functions for init/exit instead of moving the variables into the driver. Signed-off-by: Rex Zhu Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 2 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 5 ++++- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 25 +++++++++++++---------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 2a009c398dcb..992f00b65be4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -602,6 +602,8 @@ int amdgpu_sync_wait(struct amdgpu_sync *sync); void amdgpu_sync_free(struct amdgpu_sync *sync); int amdgpu_sync_init(void); void amdgpu_sync_fini(void); +int amdgpu_fence_slab_init(void); +void amdgpu_fence_slab_fini(void); /* * GART structures, functions & helpers diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 0dee008bcc57..6ff587900cf5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -565,9 +565,12 @@ static struct pci_driver amdgpu_kms_pci_driver = { .driver.pm = &amdgpu_pm_ops, }; + + static int __init amdgpu_init(void) { amdgpu_sync_init(); + amdgpu_fence_slab_init(); if (vgacon_text_force()) { DRM_ERROR("VGACON disables amdgpu kernel modesetting.\n"); return -EINVAL; @@ -578,7 +581,6 @@ static int __init amdgpu_init(void) driver->driver_features |= DRIVER_MODESET; driver->num_ioctls = amdgpu_max_kms_ioctl; amdgpu_register_atpx_handler(); - /* let modprobe override vga console setting */ return drm_pci_init(driver, pdriver); } @@ -589,6 +591,7 @@ static void __exit amdgpu_exit(void) drm_pci_exit(driver, pdriver); amdgpu_unregister_atpx_handler(); amdgpu_sync_fini(); + amdgpu_fence_slab_fini(); } module_init(amdgpu_init); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index ba9c04283d01..7eb2fca871e8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -55,8 +55,21 @@ struct amdgpu_fence { }; static struct kmem_cache *amdgpu_fence_slab; -static atomic_t amdgpu_fence_slab_ref = ATOMIC_INIT(0); +int amdgpu_fence_slab_init(void) +{ + amdgpu_fence_slab = kmem_cache_create( + "amdgpu_fence", sizeof(struct amdgpu_fence), 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!amdgpu_fence_slab) + return -ENOMEM; + return 0; +} + +void amdgpu_fence_slab_fini(void) +{ + kmem_cache_destroy(amdgpu_fence_slab); +} /* * Cast helper */ @@ -396,13 +409,6 @@ int amdgpu_fence_driver_init_ring(struct amdgpu_ring *ring, */ int amdgpu_fence_driver_init(struct amdgpu_device *adev) { - if (atomic_inc_return(&amdgpu_fence_slab_ref) == 1) { - amdgpu_fence_slab = kmem_cache_create( - "amdgpu_fence", sizeof(struct amdgpu_fence), 0, - SLAB_HWCACHE_ALIGN, NULL); - if (!amdgpu_fence_slab) - return -ENOMEM; - } if (amdgpu_debugfs_fence_init(adev)) dev_err(adev->dev, "fence debugfs file creation failed\n"); @@ -441,9 +447,6 @@ void amdgpu_fence_driver_fini(struct amdgpu_device *adev) kfree(ring->fence_drv.fences); ring->fence_drv.initialized = false; } - - if (atomic_dec_and_test(&amdgpu_fence_slab_ref)) - kmem_cache_destroy(amdgpu_fence_slab); } /** From bd6080d662f08b4c4540c4e3735f0738cab7004f Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Tue, 10 May 2016 14:39:09 -0400 Subject: [PATCH 130/393] drm/amd/powerplay: fix a bug on updating sclk for Fiji Update sclk smc table rather than mclk smc table for sclk updates. Reviewed-by: Alex Deucher Signed-off-by: Eric Huang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c index 382223b2f047..6f1bad41381a 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c @@ -4314,7 +4314,7 @@ static int fiji_populate_and_upload_sclk_mclk_dpm_levels( if (data->need_update_smu7_dpm_table & (DPMTABLE_OD_UPDATE_SCLK + DPMTABLE_UPDATE_SCLK)) { - result = fiji_populate_all_memory_levels(hwmgr); + result = fiji_populate_all_graphic_levels(hwmgr); PP_ASSERT_WITH_CODE((0 == result), "Failed to populate SCLK during PopulateNewDPMClocksStates Function!", return result); From e2ed8a132a70dc61ef6d9979bec4f37a3abbba4b Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Thu, 12 May 2016 14:41:04 -0400 Subject: [PATCH 131/393] drm/amd/powerplay: fix a bug on updating sclk for Tonga Update sclk smc table rather than mclk smc table for sclk updates. Reviewed-by: Alex Deucher Signed-off-by: Eric Huang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c index 1faad92b50d3..cb2833527404 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c @@ -5445,7 +5445,7 @@ static int tonga_populate_and_upload_sclk_mclk_dpm_levels(struct pp_hwmgr *hwmgr } if (data->need_update_smu7_dpm_table & (DPMTABLE_OD_UPDATE_SCLK + DPMTABLE_UPDATE_SCLK)) { - result = tonga_populate_all_memory_levels(hwmgr); + result = tonga_populate_all_graphic_levels(hwmgr); PP_ASSERT_WITH_CODE((0 == result), "Failed to populate SCLK during PopulateNewDPMClocksStates Function!", return result); From 0c7f77afb7f6e74af899584ac672c8d9e63058b0 Mon Sep 17 00:00:00 2001 From: Easwar Hariharan Date: Thu, 12 May 2016 10:22:33 -0700 Subject: [PATCH 132/393] IB/hfi1: Ignore non-temperature warnings on a downed link QSFP modules can raise an interrupt to inform us of expected conditions while the link is down, such as RX power low. Actively ignore these conditions when the link is down as they only add reporting noise. Continue reporting conditions that are valid at all times, such as temperature alarms and warnings. Reviewed-by: Dean Luick Signed-off-by: Easwar Hariharan Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/chip.c | 17 +++++++++++------ drivers/staging/rdma/hfi1/diag.c | 2 +- drivers/staging/rdma/hfi1/hfi.h | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/staging/rdma/hfi1/chip.c b/drivers/staging/rdma/hfi1/chip.c index dcae8e723f98..f6ae0ba3a3b8 100644 --- a/drivers/staging/rdma/hfi1/chip.c +++ b/drivers/staging/rdma/hfi1/chip.c @@ -6105,7 +6105,7 @@ int acquire_lcb_access(struct hfi1_devdata *dd, int sleep_ok) } /* this access is valid only when the link is up */ - if ((ppd->host_link_state & HLS_UP) == 0) { + if (ppd->host_link_state & HLS_DOWN) { dd_dev_info(dd, "%s: link state %s not up\n", __func__, link_state_name(ppd->host_link_state)); ret = -EBUSY; @@ -7429,7 +7429,7 @@ void apply_link_downgrade_policy(struct hfi1_pportdata *ppd, int refresh_widths) retry: mutex_lock(&ppd->hls_lock); /* only apply if the link is up */ - if (!(ppd->host_link_state & HLS_UP)) { + if (ppd->host_link_state & HLS_DOWN) { /* still going up..wait and retry */ if (ppd->host_link_state & HLS_GOING_UP) { if (++tries < 1000) { @@ -9252,6 +9252,12 @@ static int handle_qsfp_error_conditions(struct hfi1_pportdata *ppd, dd_dev_info(dd, "%s: QSFP cable temperature too low\n", __func__); + /* + * The remaining alarms/warnings don't matter if the link is down. + */ + if (ppd->host_link_state & HLS_DOWN) + return 0; + if ((qsfp_interrupt_status[1] & QSFP_HIGH_VCC_ALARM) || (qsfp_interrupt_status[1] & QSFP_HIGH_VCC_WARNING)) dd_dev_info(dd, "%s: QSFP supply voltage too high\n", @@ -9346,9 +9352,8 @@ void qsfp_event(struct work_struct *work) return; /* - * Turn DC back on after cables has been - * re-inserted. Up until now, the DC has been in - * reset to save power. + * Turn DC back on after cable has been re-inserted. Up until + * now, the DC has been in reset to save power. */ dc_start(dd); @@ -10074,7 +10079,7 @@ u32 driver_physical_state(struct hfi1_pportdata *ppd) */ u32 driver_logical_state(struct hfi1_pportdata *ppd) { - if (ppd->host_link_state && !(ppd->host_link_state & HLS_UP)) + if (ppd->host_link_state && (ppd->host_link_state & HLS_DOWN)) return IB_PORT_DOWN; switch (ppd->host_link_state & HLS_UP) { diff --git a/drivers/staging/rdma/hfi1/diag.c b/drivers/staging/rdma/hfi1/diag.c index bb2409ad891a..3a679e46fa7d 100644 --- a/drivers/staging/rdma/hfi1/diag.c +++ b/drivers/staging/rdma/hfi1/diag.c @@ -998,7 +998,7 @@ static long hfi1_assign_snoop_link_credits(struct hfi1_pportdata *ppd, u16 per_vl_credits; __be16 be_per_vl_credits; - if (!(ppd->host_link_state & HLS_UP)) + if (ppd->host_link_state & HLS_DOWN) goto err_exit; if (total_credits < vl15_credits) goto err_exit; diff --git a/drivers/staging/rdma/hfi1/hfi.h b/drivers/staging/rdma/hfi1/hfi.h index 7b78d56de7f5..4aac75f7ce59 100644 --- a/drivers/staging/rdma/hfi1/hfi.h +++ b/drivers/staging/rdma/hfi1/hfi.h @@ -453,6 +453,7 @@ struct rvt_sge_state; #define HLS_LINK_COOLDOWN BIT(__HLS_LINK_COOLDOWN_BP) #define HLS_UP (HLS_UP_INIT | HLS_UP_ARMED | HLS_UP_ACTIVE) +#define HLS_DOWN ~(HLS_UP) /* use this MTU size if none other is given */ #define HFI1_DEFAULT_ACTIVE_MTU 10240 From 9775a991f9bdbdde3cc38e553326755af5b2b2a9 Mon Sep 17 00:00:00 2001 From: Easwar Hariharan Date: Thu, 12 May 2016 10:22:39 -0700 Subject: [PATCH 133/393] IB/hfi1: Wait for QSFP modules to initialize The function level reset in init_chip() and subsequent write of all 1s to the ASIC_QSFP registers effectively resets attached active and optical QSFP modules that pay attention to the RESET_N pin. We subsequently try to access the QSFP management interface to qualify and tune the channel and fabric SerDes before enough time (2 seconds per SFF 8679 spec for QSFP28 modules) has elapsed for the module to finish initialization. This fails and causes the failure of the channel tuning algorithm, preventing us from bringing the link up. This patch checks the port type prior to beginning channel and SerDes tuning, and if found to be QSFP, watches for the QSFP initialization complete interrupt, with a maximum timeout of 2 seconds, to allow the initialization to complete. Reviewed-by: Dean Luick Signed-off-by: Easwar Hariharan Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/chip.c | 10 +++++++++- drivers/staging/rdma/hfi1/platform.c | 17 +++++++++++------ drivers/staging/rdma/hfi1/platform.h | 1 + 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/staging/rdma/hfi1/chip.c b/drivers/staging/rdma/hfi1/chip.c index f6ae0ba3a3b8..ce4c275b60f2 100644 --- a/drivers/staging/rdma/hfi1/chip.c +++ b/drivers/staging/rdma/hfi1/chip.c @@ -9485,7 +9485,15 @@ int bringup_serdes(struct hfi1_pportdata *ppd) return ret; } - /* tune the SERDES to a ballpark setting for + get_port_type(ppd); + if (ppd->port_type == PORT_TYPE_QSFP) { + set_qsfp_int_n(ppd, 0); + wait_for_qsfp_init(ppd); + set_qsfp_int_n(ppd, 1); + } + + /* + * Tune the SerDes to a ballpark setting for * optimal signal and bit error rate * Needs to be done before starting the link */ diff --git a/drivers/staging/rdma/hfi1/platform.c b/drivers/staging/rdma/hfi1/platform.c index 8fe8a205b5bb..dfa413cfa351 100644 --- a/drivers/staging/rdma/hfi1/platform.c +++ b/drivers/staging/rdma/hfi1/platform.c @@ -87,6 +87,17 @@ void free_platform_config(struct hfi1_devdata *dd) */ } +void get_port_type(struct hfi1_pportdata *ppd) +{ + int ret; + + ret = get_platform_config_field(ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, + PORT_TABLE_PORT_TYPE, &ppd->port_type, + 4); + if (ret) + ppd->port_type = PORT_TYPE_UNKNOWN; +} + int set_qsfp_tx(struct hfi1_pportdata *ppd, int on) { u8 tx_ctrl_byte = on ? 0x0 : 0xF; @@ -784,12 +795,6 @@ void tune_serdes(struct hfi1_pportdata *ppd) return; } - ret = get_platform_config_field(ppd->dd, PLATFORM_CONFIG_PORT_TABLE, 0, - PORT_TABLE_PORT_TYPE, &ppd->port_type, - 4); - if (ret) - ppd->port_type = PORT_TYPE_UNKNOWN; - switch (ppd->port_type) { case PORT_TYPE_DISCONNECTED: ppd->offline_disabled_reason = diff --git a/drivers/staging/rdma/hfi1/platform.h b/drivers/staging/rdma/hfi1/platform.h index 19620cf546d5..e2c21613c326 100644 --- a/drivers/staging/rdma/hfi1/platform.h +++ b/drivers/staging/rdma/hfi1/platform.h @@ -298,6 +298,7 @@ enum link_tuning_encoding { /* platform.c */ void get_platform_config(struct hfi1_devdata *dd); void free_platform_config(struct hfi1_devdata *dd); +void get_port_type(struct hfi1_pportdata *ppd); int set_qsfp_tx(struct hfi1_pportdata *ppd, int on); void tune_serdes(struct hfi1_pportdata *ppd); From 27a340f6b206dd2a0208d3bc4605c0708b55d7b4 Mon Sep 17 00:00:00 2001 From: Easwar Hariharan Date: Thu, 12 May 2016 10:22:45 -0700 Subject: [PATCH 134/393] IB/hfi1: Correct external device configuration shift The external device configuration was incorrectly shifted to byte 3 of the 32 bit DC_HOST_COMM_SETTINGS instead of byte 0. This patch corrects the shift and provides the cable capability information in byte 0. Reviewed-by: Dean Luick Signed-off-by: Easwar Hariharan Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/platform.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/rdma/hfi1/platform.c b/drivers/staging/rdma/hfi1/platform.c index dfa413cfa351..ae0e4985cc7e 100644 --- a/drivers/staging/rdma/hfi1/platform.c +++ b/drivers/staging/rdma/hfi1/platform.c @@ -575,8 +575,8 @@ static void apply_tunings( ret = read_8051_config(ppd->dd, DC_HOST_COMM_SETTINGS, GENERAL_CONFIG, &config_data); /* Clear, then set the external device config field */ - config_data &= ~(0xFF << 24); - config_data |= (external_device_config << 24); + config_data &= ~(u32)0xFF; + config_data |= external_device_config; ret = load_8051_config(ppd->dd, DC_HOST_COMM_SETTINGS, GENERAL_CONFIG, config_data); if (ret != HCMD_SUCCESS) From 1ebe79c9484bb1b9bd51b3dd6e82b8ff87ebaeba Mon Sep 17 00:00:00 2001 From: Dean Luick Date: Thu, 12 May 2016 10:22:51 -0700 Subject: [PATCH 135/393] IB/hfi1: Remove no-op QSFP reset code The RESET_N bit of the ASIC_QSFPn_OE register is not used by the hardware. Remove code that tries to use it - it does nothing. Reviewed-by: Easwar Hariharan Signed-off-by: Dean Luick Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/chip.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/staging/rdma/hfi1/chip.c b/drivers/staging/rdma/hfi1/chip.c index ce4c275b60f2..ec0953ec7ee3 100644 --- a/drivers/staging/rdma/hfi1/chip.c +++ b/drivers/staging/rdma/hfi1/chip.c @@ -9212,9 +9212,6 @@ void reset_qsfp(struct hfi1_pportdata *ppd) /* Reset the QSFP */ mask = (u64)QSFP_HFI0_RESET_N; - qsfp_mask = read_csr(dd, dd->hfi1_id ? ASIC_QSFP2_OE : ASIC_QSFP1_OE); - qsfp_mask |= mask; - write_csr(dd, dd->hfi1_id ? ASIC_QSFP2_OE : ASIC_QSFP1_OE, qsfp_mask); qsfp_mask = read_csr(dd, dd->hfi1_id ? ASIC_QSFP2_OUT : ASIC_QSFP1_OUT); From 17f15bf66884b9e33c5005ee0149b039af8f7af2 Mon Sep 17 00:00:00 2001 From: Mike Marciniszyn Date: Thu, 12 May 2016 10:22:57 -0700 Subject: [PATCH 136/393] IB/hfi1: Fix pio wait counter double increment The code unconditionlly increments the pio wait counter making the counter inacurate and unusable. Fixes: 14553ca11039 ("staging/rdma/hfi1: Adaptive PIO for short messages") Reviewed-by: Dennis Dalessandro Signed-off-by: Mike Marciniszyn Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/verbs.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/staging/rdma/hfi1/verbs.c b/drivers/staging/rdma/hfi1/verbs.c index 9cdc85fa366f..f3531fdfef01 100644 --- a/drivers/staging/rdma/hfi1/verbs.c +++ b/drivers/staging/rdma/hfi1/verbs.c @@ -946,7 +946,6 @@ static int pio_wait(struct rvt_qp *qp, dev->n_piowait += !!(flag & RVT_S_WAIT_PIO); dev->n_piodrain += !!(flag & RVT_S_WAIT_PIO_DRAIN); - dev->n_piowait++; qp->s_flags |= flag; was_empty = list_empty(&sc->piowait); list_add_tail(&priv->s_iowait.list, &sc->piowait); From b96b040445f5d84fb8aa2ff986be71f5069c976e Mon Sep 17 00:00:00 2001 From: Mike Marciniszyn Date: Thu, 12 May 2016 10:23:03 -0700 Subject: [PATCH 137/393] IB/hfi1: Fix potential panic with sdma drained mechanism The guard is backwards, potentially causing the SDMA client to panic if a wait structure was not specified. psm and verbs are not exposed to the issue, but fix the code just to be correct. Fixes: a545f5308b6c ("staging/rdma/hfi: fix CQ completion order issue") Reviewed-by: Dennis Dalessandro Signed-off-by: Mike Marciniszyn Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/sdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/rdma/hfi1/sdma.c b/drivers/staging/rdma/hfi1/sdma.c index abb8ebc1fcac..e938017f78e3 100644 --- a/drivers/staging/rdma/hfi1/sdma.c +++ b/drivers/staging/rdma/hfi1/sdma.c @@ -376,7 +376,7 @@ static inline void complete_tx(struct sdma_engine *sde, sdma_txclean(sde->dd, tx); if (complete) (*complete)(tx, res); - if (iowait_sdma_dec(wait) && wait) + if (wait && iowait_sdma_dec(wait)) iowait_drain_wakeup(wait); } From 67caea1fec85266590dddfa431e1cb000bc942f4 Mon Sep 17 00:00:00 2001 From: Mitko Haralanov Date: Thu, 12 May 2016 10:23:09 -0700 Subject: [PATCH 138/393] IB/hfi1: Improve performance of interval RB trees The interval RB tree management functions use handlers to store user-specific callback for the various tree operations. These handlers are put on a doubly-linked list. When a RB tree function is called, the list is searched for the handler of the particular tree. The list which holds the handlers is modified very rarely - when a handler is created and when a handler is removed. On the other hand, it is searched very often. This a perfect usage scenario for RCU. The result is a much lower overhead of traversing the list as most of the time no locking will be required. Reviewed-by: Dean Luick Reviewed-by: Mike Marciniszyn Signed-off-by: Mitko Haralanov Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/mmu_rb.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/staging/rdma/hfi1/mmu_rb.c b/drivers/staging/rdma/hfi1/mmu_rb.c index 2b0e91d3093d..b7a80aa1ae30 100644 --- a/drivers/staging/rdma/hfi1/mmu_rb.c +++ b/drivers/staging/rdma/hfi1/mmu_rb.c @@ -45,6 +45,7 @@ * */ #include +#include #include #include @@ -97,7 +98,6 @@ static unsigned long mmu_node_last(struct mmu_rb_node *node) int hfi1_mmu_rb_register(struct rb_root *root, struct mmu_rb_ops *ops) { struct mmu_rb_handler *handlr; - unsigned long flags; if (!ops->invalidate) return -EINVAL; @@ -111,9 +111,9 @@ int hfi1_mmu_rb_register(struct rb_root *root, struct mmu_rb_ops *ops) INIT_HLIST_NODE(&handlr->mn.hlist); spin_lock_init(&handlr->lock); handlr->mn.ops = &mn_opts; - spin_lock_irqsave(&mmu_rb_lock, flags); - list_add_tail(&handlr->list, &mmu_rb_handlers); - spin_unlock_irqrestore(&mmu_rb_lock, flags); + spin_lock(&mmu_rb_lock); + list_add_tail_rcu(&handlr->list, &mmu_rb_handlers); + spin_unlock(&mmu_rb_lock); return mmu_notifier_register(&handlr->mn, current->mm); } @@ -130,9 +130,10 @@ void hfi1_mmu_rb_unregister(struct rb_root *root) if (current->mm) mmu_notifier_unregister(&handler->mn, current->mm); - spin_lock_irqsave(&mmu_rb_lock, flags); - list_del(&handler->list); - spin_unlock_irqrestore(&mmu_rb_lock, flags); + spin_lock(&mmu_rb_lock); + list_del_rcu(&handler->list); + spin_unlock(&mmu_rb_lock); + synchronize_rcu(); spin_lock_irqsave(&handler->lock, flags); if (!RB_EMPTY_ROOT(root)) { @@ -271,16 +272,15 @@ void hfi1_mmu_rb_remove(struct rb_root *root, struct mmu_rb_node *node) static struct mmu_rb_handler *find_mmu_handler(struct rb_root *root) { struct mmu_rb_handler *handler; - unsigned long flags; - spin_lock_irqsave(&mmu_rb_lock, flags); - list_for_each_entry(handler, &mmu_rb_handlers, list) { + rcu_read_lock(); + list_for_each_entry_rcu(handler, &mmu_rb_handlers, list) { if (handler->root == root) goto unlock; } handler = NULL; unlock: - spin_unlock_irqrestore(&mmu_rb_lock, flags); + rcu_read_unlock(); return handler; } From 63d0b4a5fc5f589c6e980fc5d43c220e74a6f06c Mon Sep 17 00:00:00 2001 From: Jubin John Date: Thu, 12 May 2016 10:23:16 -0700 Subject: [PATCH 139/393] IB/hfi1: Remove unnecessary header While running perftests, there is a significant utilization of the random number daemon. This is due to the linux/random.h header being included in qp.c and verbs.c. However, none of the functions from this header are being used in these files, so remove the unnecessary header. Reviewed-by: Mike Marciniszyn Signed-off-by: Jubin John Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/qp.c | 1 - drivers/staging/rdma/hfi1/verbs.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/staging/rdma/hfi1/qp.c b/drivers/staging/rdma/hfi1/qp.c index 91eb42316df9..c62650892e00 100644 --- a/drivers/staging/rdma/hfi1/qp.c +++ b/drivers/staging/rdma/hfi1/qp.c @@ -49,7 +49,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/staging/rdma/hfi1/verbs.c b/drivers/staging/rdma/hfi1/verbs.c index f3531fdfef01..ae92ac5ff232 100644 --- a/drivers/staging/rdma/hfi1/verbs.c +++ b/drivers/staging/rdma/hfi1/verbs.c @@ -52,7 +52,6 @@ #include #include #include -#include #include #include "hfi.h" From 02ba00c0bbfbbe8abd81abd38302d400b59e220f Mon Sep 17 00:00:00 2001 From: Jubin John Date: Thu, 12 May 2016 10:23:22 -0700 Subject: [PATCH 140/393] IB/hfi1: Fix hfi_rcvhdr tracepoint The hfi_rcvhdr tracepoint has the ctxt and eflags switched in the prototype of the trace event, compared to the args and usage of the trace function. Fix this by swapping these 2 fields in the trace event prototype. Reviewed-by: Dennis Dalessandro Reviewed-by: Mike Marciniszyn Signed-off-by: Jubin John Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/trace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/rdma/hfi1/trace.h b/drivers/staging/rdma/hfi1/trace.h index 963dc948c38a..42bcfc3e15d5 100644 --- a/drivers/staging/rdma/hfi1/trace.h +++ b/drivers/staging/rdma/hfi1/trace.h @@ -74,8 +74,8 @@ __print_symbolic(etype, \ TRACE_EVENT(hfi1_rcvhdr, TP_PROTO(struct hfi1_devdata *dd, - u64 eflags, u32 ctxt, + u64 eflags, u32 etype, u32 hlen, u32 tlen, From cdbff5042d69bbe3f9840bea03863c93f93c88fa Mon Sep 17 00:00:00 2001 From: Mike Marciniszyn Date: Thu, 12 May 2016 10:23:28 -0700 Subject: [PATCH 141/393] IB/rdmavt: Increase CQ callback thread priority The priority of the send engines is higher than the CQ completion thread potentially causing completions to be starved for very fast interfaces. Change the CQ kthread to match the send engine threads to minimize this delay for ULP completion processing. Reviewed-by: Dennis Dalessandro Signed-off-by: Mike Marciniszyn Signed-off-by: Doug Ledford --- drivers/infiniband/sw/rdmavt/cq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/infiniband/sw/rdmavt/cq.c b/drivers/infiniband/sw/rdmavt/cq.c index b1ffc8b4a6c0..6ca6fa80dd6e 100644 --- a/drivers/infiniband/sw/rdmavt/cq.c +++ b/drivers/infiniband/sw/rdmavt/cq.c @@ -525,6 +525,7 @@ int rvt_driver_cq_init(struct rvt_dev_info *rdi) return PTR_ERR(task); } + set_user_nice(task, MIN_NICE); cpu = cpumask_first(cpumask_of_node(rdi->dparms.node)); kthread_bind(task, cpu); wake_up_process(task); From cde10afa6c8d0db75a3dc038ec46ae901c51cfab Mon Sep 17 00:00:00 2001 From: Jakub Pawlak Date: Thu, 12 May 2016 10:23:35 -0700 Subject: [PATCH 142/393] IB/hfi1: Correct log message strings Remove "IB" keyword from log messages. Correct comment for thermal sensor init function. Reviewed-by: Dean Luick Signed-off-by: Jakub Pawlak Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/chip.c | 2 +- drivers/staging/rdma/hfi1/driver.c | 2 +- drivers/staging/rdma/hfi1/hfi.h | 5 ++--- drivers/staging/rdma/hfi1/sysfs.c | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/staging/rdma/hfi1/chip.c b/drivers/staging/rdma/hfi1/chip.c index ec0953ec7ee3..d8cc329901d0 100644 --- a/drivers/staging/rdma/hfi1/chip.c +++ b/drivers/staging/rdma/hfi1/chip.c @@ -14588,7 +14588,7 @@ u64 create_pbc(struct hfi1_pportdata *ppd, u64 flags, int srate_mbs, u32 vl, (reason), (ret)) /* - * Initialize the Avago Thermal sensor. + * Initialize the thermal sensor. * * After initialization, enable polling of thermal sensor through * SBus interface. In order for this to work, the SBus Master diff --git a/drivers/staging/rdma/hfi1/driver.c b/drivers/staging/rdma/hfi1/driver.c index 700c6fa3a633..c75b0ae688f8 100644 --- a/drivers/staging/rdma/hfi1/driver.c +++ b/drivers/staging/rdma/hfi1/driver.c @@ -1161,7 +1161,7 @@ int hfi1_set_lid(struct hfi1_pportdata *ppd, u32 lid, u8 lmc) ppd->lmc = lmc; hfi1_set_ib_cfg(ppd, HFI1_IB_CFG_LIDLMC, 0); - dd_dev_info(dd, "IB%u:%u got a lid: 0x%x\n", dd->unit, ppd->port, lid); + dd_dev_info(dd, "port %u: got a lid: 0x%x\n", ppd->port, lid); return 0; } diff --git a/drivers/staging/rdma/hfi1/hfi.h b/drivers/staging/rdma/hfi1/hfi.h index 4aac75f7ce59..337bd2dc2dbf 100644 --- a/drivers/staging/rdma/hfi1/hfi.h +++ b/drivers/staging/rdma/hfi1/hfi.h @@ -1883,9 +1883,8 @@ static inline u64 hfi1_pkt_base_sdma_integrity(struct hfi1_devdata *dd) get_unit_name((dd)->unit), ##__VA_ARGS__) #define hfi1_dev_porterr(dd, port, fmt, ...) \ - dev_err(&(dd)->pcidev->dev, "%s: IB%u:%u " fmt, \ - get_unit_name((dd)->unit), (dd)->unit, (port), \ - ##__VA_ARGS__) + dev_err(&(dd)->pcidev->dev, "%s: port %u: " fmt, \ + get_unit_name((dd)->unit), (port), ##__VA_ARGS__) /* * this is used for formatting hw error messages... diff --git a/drivers/staging/rdma/hfi1/sysfs.c b/drivers/staging/rdma/hfi1/sysfs.c index 8cd6df8634ad..91fc2aed6aed 100644 --- a/drivers/staging/rdma/hfi1/sysfs.c +++ b/drivers/staging/rdma/hfi1/sysfs.c @@ -721,8 +721,8 @@ int hfi1_create_port_files(struct ib_device *ibdev, u8 port_num, } dd_dev_info(dd, - "IB%u: Congestion Control Agent enabled for port %d\n", - dd->unit, port_num); + "Congestion Control Agent enabled for port %d\n", + port_num); return 0; From f036780be8ac7abee912bd1eeb459230e6bcc878 Mon Sep 17 00:00:00 2001 From: Dean Luick Date: Thu, 12 May 2016 10:23:41 -0700 Subject: [PATCH 143/393] IB/hfi1: Immediately apply congestion setting MAD The handling of the congestion setting MAD packet only saved off the values, waiting for a congestion control table packet before going active. Instead, immediately apply the values. Reviewed-by: Dennis Dalessandro Signed-off-by: Dean Luick Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/mad.c | 93 +++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/drivers/staging/rdma/hfi1/mad.c b/drivers/staging/rdma/hfi1/mad.c index ed58cf21e790..17882dc8650e 100644 --- a/drivers/staging/rdma/hfi1/mad.c +++ b/drivers/staging/rdma/hfi1/mad.c @@ -3363,6 +3363,50 @@ static int __subn_get_opa_cong_setting(struct opa_smp *smp, u32 am, return reply((struct ib_mad_hdr *)smp); } +/* + * Apply congestion control information stored in the ppd to the + * active structure. + */ +static void apply_cc_state(struct hfi1_pportdata *ppd) +{ + struct cc_state *old_cc_state, *new_cc_state; + + new_cc_state = kzalloc(sizeof(*new_cc_state), GFP_KERNEL); + if (!new_cc_state) + return; + + /* + * Hold the lock for updating *and* to prevent ppd information + * from changing during the update. + */ + spin_lock(&ppd->cc_state_lock); + + old_cc_state = get_cc_state(ppd); + if (!old_cc_state) { + /* never active, or shutting down */ + spin_unlock(&ppd->cc_state_lock); + kfree(new_cc_state); + return; + } + + *new_cc_state = *old_cc_state; + + new_cc_state->cct.ccti_limit = ppd->total_cct_entry - 1; + memcpy(new_cc_state->cct.entries, ppd->ccti_entries, + ppd->total_cct_entry * sizeof(struct ib_cc_table_entry)); + + new_cc_state->cong_setting.port_control = IB_CC_CCS_PC_SL_BASED; + new_cc_state->cong_setting.control_map = ppd->cc_sl_control_map; + memcpy(new_cc_state->cong_setting.entries, ppd->congestion_entries, + OPA_MAX_SLS * sizeof(struct opa_congestion_setting_entry)); + + rcu_assign_pointer(ppd->cc_state, new_cc_state); + + spin_unlock(&ppd->cc_state_lock); + + call_rcu(&old_cc_state->rcu, cc_state_reclaim); +} + static int __subn_set_opa_cong_setting(struct opa_smp *smp, u32 am, u8 *data, struct ib_device *ibdev, u8 port, u32 *resp_len) @@ -3374,6 +3418,11 @@ static int __subn_set_opa_cong_setting(struct opa_smp *smp, u32 am, u8 *data, struct opa_congestion_setting_entry_shadow *entries; int i; + /* + * Save details from packet into the ppd. Hold the cc_state_lock so + * our information is consistent with anyone trying to apply the state. + */ + spin_lock(&ppd->cc_state_lock); ppd->cc_sl_control_map = be32_to_cpu(p->control_map); entries = ppd->congestion_entries; @@ -3384,6 +3433,10 @@ static int __subn_set_opa_cong_setting(struct opa_smp *smp, u32 am, u8 *data, p->entries[i].trigger_threshold; entries[i].ccti_min = p->entries[i].ccti_min; } + spin_unlock(&ppd->cc_state_lock); + + /* now apply the information */ + apply_cc_state(ppd); return __subn_get_opa_cong_setting(smp, am, data, ibdev, port, resp_len); @@ -3526,7 +3579,6 @@ static int __subn_set_opa_cc_table(struct opa_smp *smp, u32 am, u8 *data, int i, j; u32 sentry, eentry; u16 ccti_limit; - struct cc_state *old_cc_state, *new_cc_state; /* sanity check n_blocks, start_block */ if (n_blocks == 0 || @@ -3546,45 +3598,20 @@ static int __subn_set_opa_cc_table(struct opa_smp *smp, u32 am, u8 *data, return reply((struct ib_mad_hdr *)smp); } - new_cc_state = kzalloc(sizeof(*new_cc_state), GFP_KERNEL); - if (!new_cc_state) - goto getit; - + /* + * Save details from packet into the ppd. Hold the cc_state_lock so + * our information is consistent with anyone trying to apply the state. + */ spin_lock(&ppd->cc_state_lock); - - old_cc_state = get_cc_state(ppd); - - if (!old_cc_state) { - spin_unlock(&ppd->cc_state_lock); - kfree(new_cc_state); - return reply((struct ib_mad_hdr *)smp); - } - - *new_cc_state = *old_cc_state; - - new_cc_state->cct.ccti_limit = ccti_limit; - - entries = ppd->ccti_entries; ppd->total_cct_entry = ccti_limit + 1; - + entries = ppd->ccti_entries; for (j = 0, i = sentry; i < eentry; j++, i++) entries[i].entry = be16_to_cpu(p->ccti_entries[j].entry); - - memcpy(new_cc_state->cct.entries, entries, - eentry * sizeof(struct ib_cc_table_entry)); - - new_cc_state->cong_setting.port_control = IB_CC_CCS_PC_SL_BASED; - new_cc_state->cong_setting.control_map = ppd->cc_sl_control_map; - memcpy(new_cc_state->cong_setting.entries, ppd->congestion_entries, - OPA_MAX_SLS * sizeof(struct opa_congestion_setting_entry)); - - rcu_assign_pointer(ppd->cc_state, new_cc_state); - spin_unlock(&ppd->cc_state_lock); - call_rcu(&old_cc_state->rcu, cc_state_reclaim); + /* now apply the information */ + apply_cc_state(ppd); -getit: return __subn_get_opa_cc_table(smp, am, data, ibdev, port, resp_len); } From 859b527f00ad1364a4152c0365e2e5ef5a6bf20a Mon Sep 17 00:00:00 2001 From: Jianxin Xiong Date: Thu, 12 May 2016 10:23:47 -0700 Subject: [PATCH 144/393] IB/hfi1: Keep SC_USER as the last send context type SC_USER needs to be the last send context type to ensure other send context types get their allocation when num_user_contexts is set to a large number. This fixes a panic when the module parameter num_user_contexts is set to 141 and larger. Reviewed-by: Dean Luick Signed-off-by: Jianxin Xiong Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/pio.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/staging/rdma/hfi1/pio.h b/drivers/staging/rdma/hfi1/pio.h index 53a08edb7f64..464cbd27b975 100644 --- a/drivers/staging/rdma/hfi1/pio.h +++ b/drivers/staging/rdma/hfi1/pio.h @@ -49,10 +49,10 @@ /* send context types */ #define SC_KERNEL 0 -#define SC_ACK 1 -#define SC_USER 2 -#define SC_VL15 3 -#define SC_MAX 4 +#define SC_VL15 1 +#define SC_ACK 2 +#define SC_USER 3 /* must be the last one: it may take all left */ +#define SC_MAX 4 /* count of send context types */ /* invalid send context index */ #define INVALID_SCI 0xff From bf77cc34f3e6905bc8841489dd628bbffe37d743 Mon Sep 17 00:00:00 2001 From: Jianxin Xiong Date: Thu, 12 May 2016 10:23:53 -0700 Subject: [PATCH 145/393] ib_pack.h: Add opcode definition for send with invalidate The opcode for "SEND Last with Invalidate" and "SEND Only with Invalidate" have been defined for RC in IBA Specification Vol 1 since Release 1.2. Add the definition to the header file in preparation of supporting these opcodes in rdmavt based drivers. Reviewed-by: Mike Marciniszyn Signed-off-by: Jianxin Xiong Signed-off-by: Doug Ledford --- include/rdma/ib_pack.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/rdma/ib_pack.h b/include/rdma/ib_pack.h index 0f3daae44bf9..b13419ce99ff 100644 --- a/include/rdma/ib_pack.h +++ b/include/rdma/ib_pack.h @@ -103,6 +103,9 @@ enum { IB_OPCODE_ATOMIC_ACKNOWLEDGE = 0x12, IB_OPCODE_COMPARE_SWAP = 0x13, IB_OPCODE_FETCH_ADD = 0x14, + /* opcode 0x15 is reserved */ + IB_OPCODE_SEND_LAST_WITH_INVALIDATE = 0x16, + IB_OPCODE_SEND_ONLY_WITH_INVALIDATE = 0x17, /* real constants follow -- see comment about above IB_OPCODE() macro for more details */ @@ -129,6 +132,8 @@ enum { IB_OPCODE(RC, ATOMIC_ACKNOWLEDGE), IB_OPCODE(RC, COMPARE_SWAP), IB_OPCODE(RC, FETCH_ADD), + IB_OPCODE(RC, SEND_LAST_WITH_INVALIDATE), + IB_OPCODE(RC, SEND_ONLY_WITH_INVALIDATE), /* UC */ IB_OPCODE(UC, SEND_FIRST), From 3923979e9bc6aa6f168a644314d6729e9f98eccc Mon Sep 17 00:00:00 2001 From: Ashutosh Dixit Date: Thu, 12 May 2016 10:24:00 -0700 Subject: [PATCH 146/393] IB/hfi1: Change hfi1_init loop to preserve error returns If one iteration of the loop causes an error return and a later iteration doesn't, the later iteration causes the earlier error condition to be lost. This could result in driver probe succeeding when it should have failed. Therefore save off the error return in the loop itself rather than outside the loop. Reviewed-by: Dennis Dalessandro Reviewed-by: Mitko Haralanov Signed-off-by: Ashutosh Dixit Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/init.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/staging/rdma/hfi1/init.c b/drivers/staging/rdma/hfi1/init.c index 502b7cf4647d..9e71abf0f40f 100644 --- a/drivers/staging/rdma/hfi1/init.c +++ b/drivers/staging/rdma/hfi1/init.c @@ -732,12 +732,12 @@ int hfi1_init(struct hfi1_devdata *dd, int reinit) lastfail = hfi1_create_rcvhdrq(dd, rcd); if (!lastfail) lastfail = hfi1_setup_eagerbufs(rcd); - if (lastfail) + if (lastfail) { dd_dev_err(dd, "failed to allocate kernel ctxt's rcvhdrq and/or egr bufs\n"); + ret = lastfail; + } } - if (lastfail) - ret = lastfail; /* Allocate enough memory for user event notification. */ len = PAGE_ALIGN(dd->chip_rcv_contexts * HFI1_MAX_SHARED_CTXTS * From eea570788ebeb817a5ecb173dafc316779120081 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Sun, 1 May 2016 18:05:31 +0530 Subject: [PATCH 147/393] staging/rdma/hfi1: use RCU_INIT_POINTER() when NULLing. It is safe to use RCU_INIT_POINTER() to NULL a pointer, instead of rcu_assign_pointer(). This results in slightly smaller/faster code. Signed-off-by: Muhammad Falak R Wani Reviewed-by: Leon Romanovsky Signed-off-by: Doug Ledford --- drivers/staging/rdma/hfi1/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/rdma/hfi1/init.c b/drivers/staging/rdma/hfi1/init.c index 9e71abf0f40f..b9beaae5953d 100644 --- a/drivers/staging/rdma/hfi1/init.c +++ b/drivers/staging/rdma/hfi1/init.c @@ -1300,7 +1300,7 @@ static void cleanup_device_data(struct hfi1_devdata *dd) spin_lock(&ppd->cc_state_lock); cc_state = get_cc_state(ppd); - rcu_assign_pointer(ppd->cc_state, NULL); + RCU_INIT_POINTER(ppd->cc_state, NULL); spin_unlock(&ppd->cc_state_lock); if (cc_state) From a8efd58815a8d8bdca409a4b8319281cd9c24f7f Mon Sep 17 00:00:00 2001 From: tom will Date: Sun, 15 May 2016 21:58:11 -0700 Subject: [PATCH 148/393] drm/radeon: fix array out of bounds When the initial value of i is greater than zero, it may cause endless loop, resulting in array out of bounds, fix it. Signed-off-by: tom will Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/kv_dpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/radeon/kv_dpm.c b/drivers/gpu/drm/radeon/kv_dpm.c index d0240743a17c..a7e978677937 100644 --- a/drivers/gpu/drm/radeon/kv_dpm.c +++ b/drivers/gpu/drm/radeon/kv_dpm.c @@ -2164,7 +2164,7 @@ static void kv_apply_state_adjust_rules(struct radeon_device *rdev, if (pi->caps_stable_p_state) { stable_p_state_sclk = (max_limits->sclk * 75) / 100; - for (i = table->count - 1; i >= 0; i++) { + for (i = table->count - 1; i >= 0; i--) { if (stable_p_state_sclk >= table->entries[i].clk) { stable_p_state_sclk = table->entries[i].clk; break; From 484f689fc9d4eb91c68f53e97dc355b1b06c3edb Mon Sep 17 00:00:00 2001 From: tom will Date: Mon, 16 May 2016 10:31:07 -0400 Subject: [PATCH 149/393] drm/amdgpu: fix array out of bounds When the initial value of i is greater than zero, it may cause endless loop, resulting in array out of bounds, fix it. This is a port of the radeon fix to amdgpu. Signed-off-by: tom will Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/kv_dpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/kv_dpm.c b/drivers/gpu/drm/amd/amdgpu/kv_dpm.c index b45f54714574..a789a863d677 100644 --- a/drivers/gpu/drm/amd/amdgpu/kv_dpm.c +++ b/drivers/gpu/drm/amd/amdgpu/kv_dpm.c @@ -2252,7 +2252,7 @@ static void kv_apply_state_adjust_rules(struct amdgpu_device *adev, if (pi->caps_stable_p_state) { stable_p_state_sclk = (max_limits->sclk * 75) / 100; - for (i = table->count - 1; i >= 0; i++) { + for (i = table->count - 1; i >= 0; i--) { if (stable_p_state_sclk >= table->entries[i].clk) { stable_p_state_sclk = table->entries[i].clk; break; From a7aabcc8a5f5e7834abb538ad97db04d29968fbe Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Fri, 13 May 2016 23:06:40 +0530 Subject: [PATCH 150/393] drm/amd/powerplay: use ARRAY_SIZE() to calculate array size. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is preferred to use ARRAY_SIZE() for size calculation, instead using sizeof(array)/sizeof(*array). It makes the code more readable. Reviewed-by: Christian König Reviewed-by: Eric Engestrom Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/smumgr/cz_smumgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/powerplay/smumgr/cz_smumgr.c b/drivers/gpu/drm/amd/powerplay/smumgr/cz_smumgr.c index da18f44fd1c8..87c023e518ab 100644 --- a/drivers/gpu/drm/amd/powerplay/smumgr/cz_smumgr.c +++ b/drivers/gpu/drm/amd/powerplay/smumgr/cz_smumgr.c @@ -639,7 +639,7 @@ static int cz_smu_populate_firmware_entries(struct pp_smumgr *smumgr) cz_smu->driver_buffer_length = 0; - for (i = 0; i < sizeof(firmware_list)/sizeof(*firmware_list); i++) { + for (i = 0; i < ARRAY_SIZE(firmware_list); i++) { firmware_type = cz_translate_firmware_enum_to_arg(smumgr, firmware_list[i]); From d4b3fa4b0881b600ddeee9dd47b27dea9709f322 Mon Sep 17 00:00:00 2001 From: Nicholas Bellinger Date: Sat, 14 May 2016 21:44:01 -0700 Subject: [PATCH 151/393] iscsi-target: Make iscsi_tpg_np driver show/store use generic code Go ahead and fold the duplicate iscsi_tpg_np driver attribute show/store functions into generic callers, invoked by current driver specific functions. This allows for future v4.8+ changes to have iscsi_target_mod use tpg_np driver attributes at runtime, instead of having the defaults hardcoded in iscsi_target_configfs.c. Also, drop the unused/legacy 'sctp' attribute for non standard RFC-3720 operation with IPPROTO_SCTP. Cc: Varun Prakash Cc: Hariprasad Shenai Cc: Christoph Hellwig Cc: Sagi Grimberg Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target_configfs.c | 199 ++++--------------- 1 file changed, 41 insertions(+), 158 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target_configfs.c b/drivers/target/iscsi/iscsi_target_configfs.c index 1b8f885dbfeb..33d811ac9539 100644 --- a/drivers/target/iscsi/iscsi_target_configfs.c +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -43,14 +43,15 @@ static inline struct iscsi_tpg_np *to_iscsi_tpg_np(struct config_item *item) return container_of(to_tpg_np(item), struct iscsi_tpg_np, se_tpg_np); } -static ssize_t lio_target_np_sctp_show(struct config_item *item, char *page) +static ssize_t lio_target_np_driver_show(struct config_item *item, char *page, + enum iscsit_transport_type type) { struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); - struct iscsi_tpg_np *tpg_np_sctp; + struct iscsi_tpg_np *tpg_np_new; ssize_t rb; - tpg_np_sctp = iscsit_tpg_locate_child_np(tpg_np, ISCSI_SCTP_TCP); - if (tpg_np_sctp) + tpg_np_new = iscsit_tpg_locate_child_np(tpg_np, type); + if (tpg_np_new) rb = sprintf(page, "1\n"); else rb = sprintf(page, "0\n"); @@ -58,19 +59,20 @@ static ssize_t lio_target_np_sctp_show(struct config_item *item, char *page) return rb; } -static ssize_t lio_target_np_sctp_store(struct config_item *item, - const char *page, size_t count) +static ssize_t lio_target_np_driver_store(struct config_item *item, + const char *page, size_t count, enum iscsit_transport_type type, + const char *mod_name) { struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); struct iscsi_np *np; struct iscsi_portal_group *tpg; - struct iscsi_tpg_np *tpg_np_sctp = NULL; + struct iscsi_tpg_np *tpg_np_new = NULL; u32 op; - int ret; + int rc; - ret = kstrtou32(page, 0, &op); - if (ret) - return ret; + rc = kstrtou32(page, 0, &op); + if (rc) + return rc; if ((op != 1) && (op != 0)) { pr_err("Illegal value for tpg_enable: %u\n", op); return -EINVAL; @@ -87,89 +89,23 @@ static ssize_t lio_target_np_sctp_store(struct config_item *item, return -EINVAL; if (op) { - /* - * Use existing np->np_sockaddr for SCTP network portal reference - */ - tpg_np_sctp = iscsit_tpg_add_network_portal(tpg, &np->np_sockaddr, - tpg_np, ISCSI_SCTP_TCP); - if (!tpg_np_sctp || IS_ERR(tpg_np_sctp)) - goto out; - } else { - tpg_np_sctp = iscsit_tpg_locate_child_np(tpg_np, ISCSI_SCTP_TCP); - if (!tpg_np_sctp) - goto out; - - ret = iscsit_tpg_del_network_portal(tpg, tpg_np_sctp); - if (ret < 0) - goto out; - } - - iscsit_put_tpg(tpg); - return count; -out: - iscsit_put_tpg(tpg); - return -EINVAL; -} - -static ssize_t lio_target_np_iser_show(struct config_item *item, char *page) -{ - struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); - struct iscsi_tpg_np *tpg_np_iser; - ssize_t rb; - - tpg_np_iser = iscsit_tpg_locate_child_np(tpg_np, ISCSI_INFINIBAND); - if (tpg_np_iser) - rb = sprintf(page, "1\n"); - else - rb = sprintf(page, "0\n"); - - return rb; -} - -static ssize_t lio_target_np_iser_store(struct config_item *item, - const char *page, size_t count) -{ - struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); - struct iscsi_np *np; - struct iscsi_portal_group *tpg; - struct iscsi_tpg_np *tpg_np_iser = NULL; - char *endptr; - u32 op; - int rc = 0; - - op = simple_strtoul(page, &endptr, 0); - if ((op != 1) && (op != 0)) { - pr_err("Illegal value for tpg_enable: %u\n", op); - return -EINVAL; - } - np = tpg_np->tpg_np; - if (!np) { - pr_err("Unable to locate struct iscsi_np from" - " struct iscsi_tpg_np\n"); - return -EINVAL; - } - - tpg = tpg_np->tpg; - if (iscsit_get_tpg(tpg) < 0) - return -EINVAL; - - if (op) { - rc = request_module("ib_isert"); - if (rc != 0) { - pr_warn("Unable to request_module for ib_isert\n"); - rc = 0; + if (strlen(mod_name)) { + rc = request_module(mod_name); + if (rc != 0) { + pr_warn("Unable to request_module for %s\n", + mod_name); + rc = 0; + } } - tpg_np_iser = iscsit_tpg_add_network_portal(tpg, &np->np_sockaddr, - tpg_np, ISCSI_INFINIBAND); - if (IS_ERR(tpg_np_iser)) { - rc = PTR_ERR(tpg_np_iser); + tpg_np_new = iscsit_tpg_add_network_portal(tpg, + &np->np_sockaddr, tpg_np, type); + if (IS_ERR(tpg_np_new)) goto out; - } } else { - tpg_np_iser = iscsit_tpg_locate_child_np(tpg_np, ISCSI_INFINIBAND); - if (tpg_np_iser) { - rc = iscsit_tpg_del_network_portal(tpg, tpg_np_iser); + tpg_np_new = iscsit_tpg_locate_child_np(tpg_np, type); + if (tpg_np_new) { + rc = iscsit_tpg_del_network_portal(tpg, tpg_np_new); if (rc < 0) goto out; } @@ -182,86 +118,33 @@ static ssize_t lio_target_np_iser_store(struct config_item *item, return rc; } -static ssize_t lio_target_np_hw_offload_show(struct config_item *item, - char *page) +static ssize_t lio_target_np_iser_show(struct config_item *item, char *page) { - struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); - struct iscsi_tpg_np *tpg_np_hw_offload; - ssize_t rb; + return lio_target_np_driver_show(item, page, ISCSI_INFINIBAND); +} - tpg_np_hw_offload = iscsit_tpg_locate_child_np(tpg_np, - ISCSI_HW_OFFLOAD); - if (tpg_np_hw_offload) - rb = sprintf(page, "1\n"); - else - rb = sprintf(page, "0\n"); +static ssize_t lio_target_np_iser_store(struct config_item *item, + const char *page, size_t count) +{ + return lio_target_np_driver_store(item, page, count, + ISCSI_INFINIBAND, "ib_isert"); +} +CONFIGFS_ATTR(lio_target_np_, iser); - return rb; +static ssize_t lio_target_np_hw_offload_show(struct config_item *item, char *page) +{ + return lio_target_np_driver_show(item, page, ISCSI_HW_OFFLOAD); } static ssize_t lio_target_np_hw_offload_store(struct config_item *item, const char *page, size_t count) { - struct iscsi_tpg_np *tpg_np = to_iscsi_tpg_np(item); - struct iscsi_np *np; - struct iscsi_portal_group *tpg; - struct iscsi_tpg_np *tpg_np_hw_offload = NULL; - u32 op; - int rc = 0; - - rc = kstrtou32(page, 0, &op); - if (rc) - return rc; - - if ((op != 1) && (op != 0)) { - pr_err("Illegal value for tpg_enable: %u\n", op); - return -EINVAL; - } - - np = tpg_np->tpg_np; - if (!np) { - pr_err("Unable to locate struct iscsi_np from" - " struct iscsi_tpg_np\n"); - return -EINVAL; - } - - tpg = tpg_np->tpg; - if (iscsit_get_tpg(tpg) < 0) - return -EINVAL; - - if (op) { - tpg_np_hw_offload = iscsit_tpg_add_network_portal(tpg, - &np->np_sockaddr, tpg_np, ISCSI_HW_OFFLOAD); - - if (IS_ERR(tpg_np_hw_offload)) { - rc = PTR_ERR(tpg_np_hw_offload); - goto out; - } - } else { - tpg_np_hw_offload = iscsit_tpg_locate_child_np(tpg_np, - ISCSI_HW_OFFLOAD); - - if (tpg_np_hw_offload) { - rc = iscsit_tpg_del_network_portal(tpg, - tpg_np_hw_offload); - if (rc < 0) - goto out; - } - } - - iscsit_put_tpg(tpg); - return count; -out: - iscsit_put_tpg(tpg); - return rc; + return lio_target_np_driver_store(item, page, count, + ISCSI_HW_OFFLOAD, ""); } - -CONFIGFS_ATTR(lio_target_np_, sctp); -CONFIGFS_ATTR(lio_target_np_, iser); CONFIGFS_ATTR(lio_target_np_, hw_offload); static struct configfs_attribute *lio_target_portal_attrs[] = { - &lio_target_np_attr_sctp, &lio_target_np_attr_iser, &lio_target_np_attr_hw_offload, NULL, From bd027d856d3d7260017d0dc932201ce5fd7561f9 Mon Sep 17 00:00:00 2001 From: Nicholas Bellinger Date: Sat, 14 May 2016 22:23:34 -0700 Subject: [PATCH 152/393] iscsi-target: Convert transport drivers to signal rdma_shutdown Instead of special casing the handful of callers that check for iser-target rdma verbs specific shutdown, use a simple flag at iscsit_transport->rdma_shutdown so each driver can signal this. Also, update iscsi-target/tcp + cxgbit to rdma_shutdown = false. Cc: Varun Prakash Cc: Hariprasad Shenai Cc: Christoph Hellwig Cc: Sagi Grimberg Signed-off-by: Nicholas Bellinger --- drivers/infiniband/ulp/isert/ib_isert.c | 1 + drivers/target/iscsi/cxgbit/cxgbit_main.c | 1 + drivers/target/iscsi/iscsi_target.c | 10 ++++------ include/target/iscsi/iscsi_transport.h | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c index 9118d7c04a71..eb32ba535434 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.c +++ b/drivers/infiniband/ulp/isert/ib_isert.c @@ -3285,6 +3285,7 @@ static void isert_get_rx_pdu(struct iscsi_conn *conn) static struct iscsit_transport iser_target_transport = { .name = "IB/iSER", .transport_type = ISCSI_INFINIBAND, + .rdma_shutdown = true, .priv_size = sizeof(struct isert_cmd), .owner = THIS_MODULE, .iscsit_setup_np = isert_setup_np, diff --git a/drivers/target/iscsi/cxgbit/cxgbit_main.c b/drivers/target/iscsi/cxgbit/cxgbit_main.c index 39162987d009..c25f5313322c 100644 --- a/drivers/target/iscsi/cxgbit/cxgbit_main.c +++ b/drivers/target/iscsi/cxgbit/cxgbit_main.c @@ -625,6 +625,7 @@ static enum target_prot_op cxgbit_get_sup_prot_ops(struct iscsi_conn *conn) static struct iscsit_transport cxgbit_transport = { .name = DRV_NAME, .transport_type = ISCSI_HW_OFFLOAD, + .rdma_shutdown = false, .priv_size = sizeof(struct cxgbit_cmd), .owner = THIS_MODULE, .iscsit_setup_np = cxgbit_setup_np, diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index bc743d2a71a2..50f3d3a0dd7b 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -670,6 +670,7 @@ static enum target_prot_op iscsit_get_sup_prot_ops(struct iscsi_conn *conn) static struct iscsit_transport iscsi_target_transport = { .name = "iSCSI/TCP", .transport_type = ISCSI_TCP, + .rdma_shutdown = false, .owner = NULL, .iscsit_setup_np = iscsit_setup_np, .iscsit_accept_np = iscsit_accept_np, @@ -4087,8 +4088,7 @@ int iscsit_close_connection( * this for iser since isert_rx_opcode() does not wait on logout failure, * and to avoid iscsi_conn pointer dereference in iser-target code. */ - if ((conn->conn_transport->transport_type == ISCSI_TCP) || - (conn->conn_transport->transport_type == ISCSI_HW_OFFLOAD)) + if (!conn->conn_transport->rdma_shutdown) complete(&conn->conn_logout_comp); if (!strcmp(current->comm, ISCSI_RX_THREAD_NAME)) { @@ -4399,8 +4399,7 @@ static void iscsit_logout_post_handler_closesession( * always sleep waiting for RX/TX thread shutdown to complete * within iscsit_close_connection(). */ - if ((conn->conn_transport->transport_type == ISCSI_TCP) || - (conn->conn_transport->transport_type == ISCSI_HW_OFFLOAD)) + if (!conn->conn_transport->rdma_shutdown) sleep = cmpxchg(&conn->tx_thread_active, true, false); atomic_set(&conn->conn_logout_remove, 0); @@ -4417,8 +4416,7 @@ static void iscsit_logout_post_handler_samecid( { int sleep = 1; - if ((conn->conn_transport->transport_type == ISCSI_TCP) || - (conn->conn_transport->transport_type == ISCSI_HW_OFFLOAD)) + if (!conn->conn_transport->rdma_shutdown) sleep = cmpxchg(&conn->tx_thread_active, true, false); atomic_set(&conn->conn_logout_remove, 0); diff --git a/include/target/iscsi/iscsi_transport.h b/include/target/iscsi/iscsi_transport.h index 3c2e10aac49c..40ac7cd80150 100644 --- a/include/target/iscsi/iscsi_transport.h +++ b/include/target/iscsi/iscsi_transport.h @@ -6,6 +6,7 @@ struct iscsit_transport { #define ISCSIT_TRANSPORT_NAME 16 char name[ISCSIT_TRANSPORT_NAME]; int transport_type; + bool rdma_shutdown; int priv_size; struct module *owner; struct list_head t_node; From ff7199b04f08e1a72637ba66cd2c05842dc592d0 Mon Sep 17 00:00:00 2001 From: Nicholas Bellinger Date: Sat, 14 May 2016 22:28:51 -0700 Subject: [PATCH 153/393] cxgbit: Use type ISCSI_CXGBIT + cxgbit tpg_np attribute Instead of having cxgbit use type ISCSI_HW_OFFLOAD + 'hw_offload' tpg_np attribute, it should be using it's own driver specific type + attribute Cc: Varun Prakash Cc: Hariprasad Shenai Cc: Christoph Hellwig Cc: Sagi Grimberg Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/cxgbit/cxgbit_main.c | 2 +- drivers/target/iscsi/iscsi_target_configfs.c | 14 +++++++------- include/target/iscsi/iscsi_target_core.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/target/iscsi/cxgbit/cxgbit_main.c b/drivers/target/iscsi/cxgbit/cxgbit_main.c index c25f5313322c..60dccd02bd85 100644 --- a/drivers/target/iscsi/cxgbit/cxgbit_main.c +++ b/drivers/target/iscsi/cxgbit/cxgbit_main.c @@ -624,7 +624,7 @@ static enum target_prot_op cxgbit_get_sup_prot_ops(struct iscsi_conn *conn) static struct iscsit_transport cxgbit_transport = { .name = DRV_NAME, - .transport_type = ISCSI_HW_OFFLOAD, + .transport_type = ISCSI_CXGBIT, .rdma_shutdown = false, .priv_size = sizeof(struct cxgbit_cmd), .owner = THIS_MODULE, diff --git a/drivers/target/iscsi/iscsi_target_configfs.c b/drivers/target/iscsi/iscsi_target_configfs.c index 33d811ac9539..923c032f0b95 100644 --- a/drivers/target/iscsi/iscsi_target_configfs.c +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -131,22 +131,22 @@ static ssize_t lio_target_np_iser_store(struct config_item *item, } CONFIGFS_ATTR(lio_target_np_, iser); -static ssize_t lio_target_np_hw_offload_show(struct config_item *item, char *page) +static ssize_t lio_target_np_cxgbit_show(struct config_item *item, char *page) { - return lio_target_np_driver_show(item, page, ISCSI_HW_OFFLOAD); + return lio_target_np_driver_show(item, page, ISCSI_CXGBIT); } -static ssize_t lio_target_np_hw_offload_store(struct config_item *item, - const char *page, size_t count) +static ssize_t lio_target_np_cxgbit_store(struct config_item *item, + const char *page, size_t count) { return lio_target_np_driver_store(item, page, count, - ISCSI_HW_OFFLOAD, ""); + ISCSI_CXGBIT, "cxgbit"); } -CONFIGFS_ATTR(lio_target_np_, hw_offload); +CONFIGFS_ATTR(lio_target_np_, cxgbit); static struct configfs_attribute *lio_target_portal_attrs[] = { &lio_target_np_attr_iser, - &lio_target_np_attr_hw_offload, + &lio_target_np_attr_cxgbit, NULL, }; diff --git a/include/target/iscsi/iscsi_target_core.h b/include/target/iscsi/iscsi_target_core.h index 110c1fde7c68..4ac24f5a3308 100644 --- a/include/target/iscsi/iscsi_target_core.h +++ b/include/target/iscsi/iscsi_target_core.h @@ -74,7 +74,7 @@ enum iscsit_transport_type { ISCSI_IWARP_TCP = 3, ISCSI_IWARP_SCTP = 4, ISCSI_INFINIBAND = 5, - ISCSI_HW_OFFLOAD = 6, + ISCSI_CXGBIT = 6, }; /* RFC-3720 7.1.4 Standard Connection State Diagram for a Target */ From 8f0dfb3d8b1120c61f6e2cc3729290db10772b2d Mon Sep 17 00:00:00 2001 From: Nicholas Bellinger Date: Sat, 27 Feb 2016 18:15:46 -0800 Subject: [PATCH 154/393] iscsi-target: Fix early sk_data_ready LOGIN_FLAGS_READY race There is a iscsi-target/tcp login race in LOGIN_FLAGS_READY state assignment that can result in frequent errors during iscsi discovery: "iSCSI Login negotiation failed." To address this bug, move the initial LOGIN_FLAGS_READY assignment ahead of iscsi_target_do_login() when handling the initial iscsi_target_start_negotiation() request PDU during connection login. As iscsi_target_do_login_rx() work_struct callback is clearing LOGIN_FLAGS_READ_ACTIVE after subsequent calls to iscsi_target_do_login(), the early sk_data_ready ahead of the first iscsi_target_do_login() expects LOGIN_FLAGS_READY to also be set for the initial login request PDU. As reported by Maged, this was first obsered using an MSFT initiator running across multiple VMWare host virtual machines with iscsi-target/tcp. Reported-by: Maged Mokhtar Tested-by: Maged Mokhtar Signed-off-by: Nicholas Bellinger --- drivers/target/iscsi/iscsi_target_nego.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c index ca061320f5eb..89d34bd6d87f 100644 --- a/drivers/target/iscsi/iscsi_target_nego.c +++ b/drivers/target/iscsi/iscsi_target_nego.c @@ -1247,16 +1247,16 @@ int iscsi_target_start_negotiation( { int ret; - ret = iscsi_target_do_login(conn, login); - if (!ret) { - if (conn->sock) { - struct sock *sk = conn->sock->sk; + if (conn->sock) { + struct sock *sk = conn->sock->sk; - write_lock_bh(&sk->sk_callback_lock); - set_bit(LOGIN_FLAGS_READY, &conn->login_flags); - write_unlock_bh(&sk->sk_callback_lock); - } - } else if (ret < 0) { + write_lock_bh(&sk->sk_callback_lock); + set_bit(LOGIN_FLAGS_READY, &conn->login_flags); + write_unlock_bh(&sk->sk_callback_lock); + } + + ret = iscsi_target_do_login(conn, login); + if (ret < 0) { cancel_delayed_work_sync(&conn->login_work); cancel_delayed_work_sync(&conn->login_cleanup_work); iscsi_target_restore_sock_callbacks(conn); From 2800b8e7d9dfca1fd9d044dcf7a046b5de5a7239 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Fri, 13 May 2016 12:38:09 -0600 Subject: [PATCH 155/393] NVMe: Allocate queues only for online cpus The driver previously requested allocating queues for the total possible number of CPUs so that blk-mq could rebalance these if CPUs were added after initialization. The number of hardware contexts can now be changed at runtime, so we only need to allocate the number of online queues since we can add more later. Suggested-by: Jeff Lien Signed-off-by: Keith Busch Reviewed-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- drivers/nvme/host/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 0f093f14d348..3c7b625a5e56 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -1394,7 +1394,7 @@ static int nvme_setup_io_queues(struct nvme_dev *dev) struct pci_dev *pdev = to_pci_dev(dev->dev); int result, i, vecs, nr_io_queues, size; - nr_io_queues = num_possible_cpus(); + nr_io_queues = num_online_cpus(); result = nvme_set_queue_count(&dev->ctrl, &nr_io_queues); if (result < 0) return result; From 014a0d609eb4721d1e416cf10da2d5602f9b34d5 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Fri, 6 May 2016 11:50:52 -0600 Subject: [PATCH 156/393] NVMe: Delete only created queues Use the online queue count instead of the number of allocated queues. The controller should just return an invalid queue identifier error to the commands if a queue wasn't created. While it's not harmful, it's still not correct. Reported-by: Saar Gross Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe --- drivers/nvme/host/pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 3c7b625a5e56..88ed43d0799c 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -1551,12 +1551,12 @@ static int nvme_delete_queue(struct nvme_queue *nvmeq, u8 opcode) static void nvme_disable_io_queues(struct nvme_dev *dev) { - int pass; + int pass, queues = dev->online_queues - 1; unsigned long timeout; u8 opcode = nvme_admin_delete_sq; for (pass = 0; pass < 2; pass++) { - int sent = 0, i = dev->queue_count - 1; + int sent = 0, i = queues; reinit_completion(&dev->ioq_wait); retry: From 921920ab32f290dafdb0359024d4587897712728 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Mon, 28 Mar 2016 16:03:21 -0600 Subject: [PATCH 157/393] NVMe: Unbind driver on failure Instead of removing the PCI device from the kernel's topology on controller failure, this patch simply requests unbinding the device from the driver. This avoids concurrently running pci removal with the hot plug event, which has been reported to be problematic when multiple surprise events occur near simultaneously. The other benefit is that we will have PCI config and memory space available to poke around for debugging a failed controller, assuming the device was not physically removed. The down side occurs if the platform and/or kernel do not support any type of surprise hot removal. The device will remain visible through sysfs (and therefore lspci), and some manual work is necessary to get the logical topology corrected. But if your platform and/or kernel don't support surprise removal, you probably shouldn't be doing that anyway. Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe --- drivers/nvme/host/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 88ed43d0799c..194e9014811b 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -1857,7 +1857,7 @@ static void nvme_remove_dead_ctrl_work(struct work_struct *work) nvme_kill_queues(&dev->ctrl); if (pci_get_drvdata(pdev)) - pci_stop_and_remove_bus_device_locked(pdev); + device_release_driver(&pdev->dev); nvme_put_ctrl(&dev->ctrl); } From d011fb3164e8694d7839f10a497f8ab6c660149a Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Mon, 4 Apr 2016 15:07:41 -0600 Subject: [PATCH 158/393] NVMe: Reduce driver log spamming Reduce error logging when no corrective action is required. Suggessted-by: Chris Petersen Signed-off-by: Keith Busch Signed-off-by: Jens Axboe --- drivers/nvme/host/pci.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 194e9014811b..4feaed591e83 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -2060,14 +2060,17 @@ static pci_ers_result_t nvme_error_detected(struct pci_dev *pdev, * shutdown the controller to quiesce. The controller will be restarted * after the slot reset through driver's slot_reset callback. */ - dev_warn(dev->ctrl.device, "error detected: state:%d\n", state); switch (state) { case pci_channel_io_normal: return PCI_ERS_RESULT_CAN_RECOVER; case pci_channel_io_frozen: + dev_warn(dev->ctrl.device, + "frozen state error detected, reset controller\n"); nvme_dev_disable(dev, false); return PCI_ERS_RESULT_NEED_RESET; case pci_channel_io_perm_failure: + dev_warn(dev->ctrl.device, + "failure state error detected, request disconnect\n"); return PCI_ERS_RESULT_DISCONNECT; } return PCI_ERS_RESULT_NEED_RESET; From 9ec3bb2f994bda9c8817856fdcbfaebe8f62fbd3 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Fri, 29 Apr 2016 15:45:18 -0600 Subject: [PATCH 159/393] NVMe: Allow user initiated rescan This exposes ioctl and sysfs methods a user can invoke to request the driver rescan a controller and its namespaces. This is less harsh than doing a controller reset, which temporarilly halts all IO, just to surface a newly attached namespace. This is mainly useful for controllers that implement the namespace management command, but do not support the namespace notify change asynchronous event notification. Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Reviewed-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- drivers/nvme/host/core.c | 15 +++++++++++++++ include/uapi/linux/nvme_ioctl.h | 1 + 2 files changed, 16 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 2de248bd462b..acc05adaa0d4 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -1212,6 +1212,9 @@ static long nvme_dev_ioctl(struct file *file, unsigned int cmd, return ctrl->ops->reset_ctrl(ctrl); case NVME_IOCTL_SUBSYS_RESET: return nvme_reset_subsystem(ctrl); + case NVME_IOCTL_RESCAN: + nvme_queue_scan(ctrl); + return 0; default: return -ENOTTY; } @@ -1239,6 +1242,17 @@ static ssize_t nvme_sysfs_reset(struct device *dev, } static DEVICE_ATTR(reset_controller, S_IWUSR, NULL, nvme_sysfs_reset); +static ssize_t nvme_sysfs_rescan(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + + nvme_queue_scan(ctrl); + return count; +} +static DEVICE_ATTR(rescan_controller, S_IWUSR, NULL, nvme_sysfs_rescan); + static ssize_t wwid_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1342,6 +1356,7 @@ nvme_show_int_function(cntlid); static struct attribute *nvme_dev_attrs[] = { &dev_attr_reset_controller.attr, + &dev_attr_rescan_controller.attr, &dev_attr_model.attr, &dev_attr_serial.attr, &dev_attr_firmware_rev.attr, diff --git a/include/uapi/linux/nvme_ioctl.h b/include/uapi/linux/nvme_ioctl.h index c4b2a3f90829..50ff21f748b6 100644 --- a/include/uapi/linux/nvme_ioctl.h +++ b/include/uapi/linux/nvme_ioctl.h @@ -61,5 +61,6 @@ struct nvme_passthru_cmd { #define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct nvme_passthru_cmd) #define NVME_IOCTL_RESET _IO('N', 0x44) #define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45) +#define NVME_IOCTL_RESCAN _IO('N', 0x46) #endif /* _UAPI_LINUX_NVME_IOCTL_H */ From 0ff9d4e1a284a9282a049bf064f123e27f838907 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Thu, 12 May 2016 08:37:14 -0600 Subject: [PATCH 160/393] NVMe: Short-cut removal on surprise hot-unplug This patch adds a new state that when set has the core automatically kill request queues prior to removing namespaces. If PCI device is not present at the time the nvme driver's remove is called, we can kill all IO queues immediately instead of waiting for the watchdog thread to do that at its polling interval. This improves scenarios where multiple hot plug events occur at the same time since it doesn't block the pci enumeration for as long. Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe --- drivers/nvme/host/core.c | 18 ++++++++++++++++++ drivers/nvme/host/nvme.h | 1 + drivers/nvme/host/pci.c | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index acc05adaa0d4..beed3940786b 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -95,6 +95,15 @@ bool nvme_change_ctrl_state(struct nvme_ctrl *ctrl, break; } break; + case NVME_CTRL_DEAD: + switch (old_state) { + case NVME_CTRL_DELETING: + changed = true; + /* FALLTHRU */ + default: + break; + } + break; default: break; } @@ -1595,6 +1604,15 @@ void nvme_remove_namespaces(struct nvme_ctrl *ctrl) { struct nvme_ns *ns, *next; + /* + * The dead states indicates the controller was not gracefully + * disconnected. In that case, we won't be able to flush any data while + * removing the namespaces' disks; fail all the queues now to avoid + * potentially having to clean up the failed sync later. + */ + if (ctrl->state == NVME_CTRL_DEAD) + nvme_kill_queues(ctrl); + mutex_lock(&ctrl->namespaces_mutex); list_for_each_entry_safe(ns, next, &ctrl->namespaces, list) nvme_ns_remove(ns); diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 114b92873894..1daa0482de0e 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -72,6 +72,7 @@ enum nvme_ctrl_state { NVME_CTRL_LIVE, NVME_CTRL_RESETTING, NVME_CTRL_DELETING, + NVME_CTRL_DEAD, }; struct nvme_ctrl { diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 4feaed591e83..3bdcf0e34fd6 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -2017,6 +2017,10 @@ static void nvme_remove(struct pci_dev *pdev) nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_DELETING); pci_set_drvdata(pdev, NULL); + + if (!pci_device_is_present(pdev)) + nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_DEAD); + flush_work(&dev->reset_work); nvme_uninit_ctrl(&dev->ctrl); nvme_dev_disable(dev, true); From 99466e708ddce8904c8635c213f2deb523ef4fb9 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Mon, 2 May 2016 15:14:24 -0600 Subject: [PATCH 161/393] NVMe: Add device ID's with stripe quirk Adds two Intel controllers that have the "stripe" quirk. Signed-off-by: Keith Busch Signed-off-by: Jens Axboe --- drivers/nvme/host/pci.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 3bdcf0e34fd6..78dca3193ca4 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -2109,6 +2109,12 @@ static const struct pci_device_id nvme_id_table[] = { { PCI_VDEVICE(INTEL, 0x0953), .driver_data = NVME_QUIRK_STRIPE_SIZE | NVME_QUIRK_DISCARD_ZEROES, }, + { PCI_VDEVICE(INTEL, 0x0a53), + .driver_data = NVME_QUIRK_STRIPE_SIZE | + NVME_QUIRK_DISCARD_ZEROES, }, + { PCI_VDEVICE(INTEL, 0x0a54), + .driver_data = NVME_QUIRK_STRIPE_SIZE | + NVME_QUIRK_DISCARD_ZEROES, }, { PCI_VDEVICE(INTEL, 0x5845), /* Qemu emulated controller */ .driver_data = NVME_QUIRK_IDENTIFY_CNS, }, { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) }, From ba36c21b0cd8b55fd7f010e9052656c2c03d9e5e Mon Sep 17 00:00:00 2001 From: Nicholas Bellinger Date: Sat, 9 Apr 2016 03:04:42 +0000 Subject: [PATCH 162/393] nvme/host: Add missing blk_integrity tag_size + flags assignments While doing recent bring-up of nvme/host with target-core T10-PI, I noticed /sys/block/nvme*/integrity/device_is_integrity_capable was false, and /sys/block/nvme*/integrity/tag_size contained a bogus value. AFAICT outside of blk_integrity_compare() for DM + MD these are informational values, but go ahead and add the missing assignments for nvme/host to match what SCSI does within sd_dif_config_host() for consistency's sake. Cc: Keith Busch Cc: Jay Freyensee Cc: Martin K. Petersen Cc: Sagi Grimberg Cc: Christoph Hellwig Signed-off-by: Nicholas Bellinger Reviewed-by: Sagi Grimberg Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe --- drivers/nvme/host/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index beed3940786b..1a51584a382b 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -729,10 +729,14 @@ static void nvme_init_integrity(struct nvme_ns *ns) switch (ns->pi_type) { case NVME_NS_DPS_PI_TYPE3: integrity.profile = &t10_pi_type3_crc; + integrity.tag_size = sizeof(u16) + sizeof(u32); + integrity.flags |= BLK_INTEGRITY_DEVICE_CAPABLE; break; case NVME_NS_DPS_PI_TYPE1: case NVME_NS_DPS_PI_TYPE2: integrity.profile = &t10_pi_type1_crc; + integrity.tag_size = sizeof(u16); + integrity.flags |= BLK_INTEGRITY_DEVICE_CAPABLE; break; default: integrity.profile = NULL; From 734711b71d3339045141a7e934f1c53d98ad0b98 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Tue, 17 May 2016 15:12:43 +0530 Subject: [PATCH 163/393] drm/amd/amdgpu : Remove unused variable Remove unused variable 'ret', and directly return 0. Reviewed-by: Junwei Zhang Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/cik_ih.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/cik_ih.c b/drivers/gpu/drm/amd/amdgpu/cik_ih.c index 845c21b1b2ee..be3d6f79a864 100644 --- a/drivers/gpu/drm/amd/amdgpu/cik_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/cik_ih.c @@ -103,7 +103,6 @@ static void cik_ih_disable_interrupts(struct amdgpu_device *adev) */ static int cik_ih_irq_init(struct amdgpu_device *adev) { - int ret = 0; int rb_bufsz; u32 interrupt_cntl, ih_cntl, ih_rb_cntl; u64 wptr_off; @@ -156,7 +155,7 @@ static int cik_ih_irq_init(struct amdgpu_device *adev) /* enable irqs */ cik_ih_enable_interrupts(adev); - return ret; + return 0; } /** From d05f2c7aeb5ec6ef6066992517d4a9af40729913 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Tue, 17 May 2016 15:12:44 +0530 Subject: [PATCH 164/393] drm/amd/amdgpu/cz_dpm: Remove unused variable Remove unused variable 'ret' from functions where it was not used anyway, and directly return 0. Reviewed-by: Junwei Zhang Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/cz_dpm.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/cz_dpm.c b/drivers/gpu/drm/amd/amdgpu/cz_dpm.c index fa4449e126e6..933e425a8154 100644 --- a/drivers/gpu/drm/amd/amdgpu/cz_dpm.c +++ b/drivers/gpu/drm/amd/amdgpu/cz_dpm.c @@ -1579,7 +1579,6 @@ static int cz_dpm_update_sclk_limit(struct amdgpu_device *adev) static int cz_dpm_set_deep_sleep_sclk_threshold(struct amdgpu_device *adev) { - int ret = 0; struct cz_power_info *pi = cz_get_pi(adev); if (pi->caps_sclk_ds) { @@ -1588,20 +1587,19 @@ static int cz_dpm_set_deep_sleep_sclk_threshold(struct amdgpu_device *adev) CZ_MIN_DEEP_SLEEP_SCLK); } - return ret; + return 0; } /* ?? without dal support, is this still needed in setpowerstate list*/ static int cz_dpm_set_watermark_threshold(struct amdgpu_device *adev) { - int ret = 0; struct cz_power_info *pi = cz_get_pi(adev); cz_send_msg_to_smc_with_parameter(adev, PPSMC_MSG_SetWatermarkFrequency, pi->sclk_dpm.soft_max_clk); - return ret; + return 0; } static int cz_dpm_enable_nbdpm(struct amdgpu_device *adev) @@ -1636,7 +1634,6 @@ static void cz_dpm_nbdpm_lm_pstate_enable(struct amdgpu_device *adev, static int cz_dpm_update_low_memory_pstate(struct amdgpu_device *adev) { - int ret = 0; struct cz_power_info *pi = cz_get_pi(adev); struct cz_ps *ps = &pi->requested_ps; @@ -1647,21 +1644,19 @@ static int cz_dpm_update_low_memory_pstate(struct amdgpu_device *adev) cz_dpm_nbdpm_lm_pstate_enable(adev, true); } - return ret; + return 0; } /* with dpm enabled */ static int cz_dpm_set_power_state(struct amdgpu_device *adev) { - int ret = 0; - cz_dpm_update_sclk_limit(adev); cz_dpm_set_deep_sleep_sclk_threshold(adev); cz_dpm_set_watermark_threshold(adev); cz_dpm_enable_nbdpm(adev); cz_dpm_update_low_memory_pstate(adev); - return ret; + return 0; } static void cz_dpm_post_set_power_state(struct amdgpu_device *adev) From 0e2b854ed4ce764e7de781901b49aedd3f7a3dfa Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Tue, 17 May 2016 15:12:45 +0530 Subject: [PATCH 165/393] drm/amd/amdgpu : Remove unused variable Remove unused variable 'ret', and directly return 0. Reviewed-by: Junwei Zhang Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/cz_ih.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/cz_ih.c b/drivers/gpu/drm/amd/amdgpu/cz_ih.c index 863cb16f6126..3d23a70b6432 100644 --- a/drivers/gpu/drm/amd/amdgpu/cz_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/cz_ih.c @@ -103,7 +103,6 @@ static void cz_ih_disable_interrupts(struct amdgpu_device *adev) */ static int cz_ih_irq_init(struct amdgpu_device *adev) { - int ret = 0; int rb_bufsz; u32 interrupt_cntl, ih_cntl, ih_rb_cntl; u64 wptr_off; @@ -157,7 +156,7 @@ static int cz_ih_irq_init(struct amdgpu_device *adev) /* enable interrupts */ cz_ih_enable_interrupts(adev); - return ret; + return 0; } /** From a280a42aa351bab5eaf8e253f172487a6785f62e Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Tue, 17 May 2016 15:12:46 +0530 Subject: [PATCH 166/393] drm/amd/amdgpu : Remove unused variable Remove unused variable 'ret', and directly return 0. Reviewed-by: Junwei Zhang Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/iceland_ih.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/iceland_ih.c b/drivers/gpu/drm/amd/amdgpu/iceland_ih.c index 39bfc52d0b42..3b8906ce3511 100644 --- a/drivers/gpu/drm/amd/amdgpu/iceland_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/iceland_ih.c @@ -103,7 +103,6 @@ static void iceland_ih_disable_interrupts(struct amdgpu_device *adev) */ static int iceland_ih_irq_init(struct amdgpu_device *adev) { - int ret = 0; int rb_bufsz; u32 interrupt_cntl, ih_cntl, ih_rb_cntl; u64 wptr_off; @@ -157,7 +156,7 @@ static int iceland_ih_irq_init(struct amdgpu_device *adev) /* enable interrupts */ iceland_ih_enable_interrupts(adev); - return ret; + return 0; } /** From 2f46b2a5c74c35f785c10fe910621662e3188682 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Tue, 17 May 2016 15:12:47 +0530 Subject: [PATCH 167/393] drm/amd/amdgpu : Remove unused variable Remove unused variable 'ret', and directly return 0. Reviewed-by: Junwei Zhang Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/tonga_ih.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c index f036af937fbc..c92055805a45 100644 --- a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c @@ -99,7 +99,6 @@ static void tonga_ih_disable_interrupts(struct amdgpu_device *adev) */ static int tonga_ih_irq_init(struct amdgpu_device *adev) { - int ret = 0; int rb_bufsz; u32 interrupt_cntl, ih_rb_cntl, ih_doorbell_rtpr; u64 wptr_off; @@ -165,7 +164,7 @@ static int tonga_ih_irq_init(struct amdgpu_device *adev) /* enable interrupts */ tonga_ih_enable_interrupts(adev); - return ret; + return 0; } /** From 1dcf480167cdba271df73a48460d60ad0f5e3845 Mon Sep 17 00:00:00 2001 From: Flora Cui Date: Mon, 16 May 2016 17:17:41 +0800 Subject: [PATCH 168/393] drm/amdgpu: add more Polaris10 DID Signed-off-by: Flora Cui Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 6ff587900cf5..f108d6df01ff 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -288,7 +288,16 @@ static const struct pci_device_id pciidlist[] = { {0x1002, 0x67FF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, /* Polaris10 */ {0x1002, 0x67C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67C2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67C4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67C7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, {0x1002, 0x67DF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67C8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67C9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67CA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67CC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, + {0x1002, 0x67CF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, {0, 0, 0} }; From d4ab989fc39897e9b6b24c4c5dd0eea288daee7e Mon Sep 17 00:00:00 2001 From: Flora Cui Date: Mon, 16 May 2016 17:18:17 +0800 Subject: [PATCH 169/393] drm/amdgpu: update Polaris10 golden setting Signed-off-by: Flora Cui Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/dce_v11_0.c | 2 +- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c b/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c index 1e0988572a2f..486207342ecf 100644 --- a/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c @@ -145,7 +145,7 @@ static const u32 polaris10_golden_settings_a11[] = { mmDCI_CLK_CNTL, 0x00000080, 0x00000000, mmFBC_DEBUG_COMP, 0x000000f0, 0x00000070, - mmFBC_MISC, 0x9f313fff, 0x14300008, + mmFBC_MISC, 0x9f313fff, 0x14302008, mmHDMI_CONTROL, 0x313f031f, 0x00000011, }; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index ef192aad7d2f..3e97e1e023cb 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -296,6 +296,7 @@ static const u32 polaris11_golden_common_all[] = static const u32 golden_settings_polaris10_a11[] = { mmATC_MISC_CG, 0x000c0fc0, 0x000c0200, + mmCB_HW_CONTROL, 0xfffdf3cf, 0x00006208, mmCB_HW_CONTROL_3, 0x000001ff, 0x00000040, mmDB_DEBUG2, 0xf00fffff, 0x00000400, mmPA_SC_ENHANCE, 0xffffffff, 0x20000001, From 35621b80085b371d1e9bb9b6de58bf173d12f31e Mon Sep 17 00:00:00 2001 From: Flora Cui Date: Tue, 17 May 2016 09:52:02 +0800 Subject: [PATCH 170/393] drm/amdgpu: Add more Polaris 11 PCI IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Flora Cui Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index f108d6df01ff..f888c015f76c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -281,11 +281,14 @@ static const struct pci_device_id pciidlist[] = { {0x1002, 0x98E4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_STONEY|AMD_IS_APU}, /* Polaris11 */ {0x1002, 0x67E0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, + {0x1002, 0x67E3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, {0x1002, 0x67E8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, - {0x1002, 0x67E9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, {0x1002, 0x67EB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, + {0x1002, 0x67EF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, {0x1002, 0x67FF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, + {0x1002, 0x67E1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, + {0x1002, 0x67E7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, + {0x1002, 0x67E9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS11}, /* Polaris10 */ {0x1002, 0x67C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, {0x1002, 0x67C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_POLARIS10}, From b9934878fdbabd7eea5dbc953b66d74decdf5fbd Mon Sep 17 00:00:00 2001 From: Flora Cui Date: Tue, 17 May 2016 09:52:22 +0800 Subject: [PATCH 171/393] drm/amdgpu: update Polaris11 golden setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Flora Cui Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/dce_v11_0.c | 2 +- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 5 +++-- drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c b/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c index 486207342ecf..cd2ec816b485 100644 --- a/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/dce_v11_0.c @@ -137,7 +137,7 @@ static const u32 polaris11_golden_settings_a11[] = mmDCI_CLK_CNTL, 0x00000080, 0x00000000, mmFBC_DEBUG_COMP, 0x000000f0, 0x00000070, mmFBC_DEBUG1, 0xffffffff, 0x00000008, - mmFBC_MISC, 0x9f313fff, 0x14300008, + mmFBC_MISC, 0x9f313fff, 0x14302008, mmHDMI_CONTROL, 0x313f031f, 0x00000011, }; diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 3e97e1e023cb..f19bab68fd83 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -267,10 +267,13 @@ static const u32 tonga_mgcg_cgcg_init[] = static const u32 golden_settings_polaris11_a11[] = { + mmCB_HW_CONTROL, 0xfffdf3cf, 0x00006208, mmCB_HW_CONTROL_3, 0x000001ff, 0x00000040, mmDB_DEBUG2, 0xf00fffff, 0x00000400, mmPA_SC_ENHANCE, 0xffffffff, 0x20000001, mmPA_SC_LINE_STIPPLE_STATE, 0x0000ff0f, 0x00000000, + mmPA_SC_RASTER_CONFIG, 0x3f3fffff, 0x16000012, + mmPA_SC_RASTER_CONFIG_1, 0x0000003f, 0x00000000, mmRLC_CGCG_CGLS_CTRL, 0x00000003, 0x0001003c, mmRLC_CGCG_CGLS_CTRL_3D, 0xffffffff, 0x0001003c, mmSQ_CONFIG, 0x07f80000, 0x07180000, @@ -284,8 +287,6 @@ static const u32 golden_settings_polaris11_a11[] = static const u32 polaris11_golden_common_all[] = { mmGRBM_GFX_INDEX, 0xffffffff, 0xe0000000, - mmPA_SC_RASTER_CONFIG, 0xffffffff, 0x16000012, - mmPA_SC_RASTER_CONFIG_1, 0xffffffff, 0x00000000, mmGB_ADDR_CONFIG, 0xffffffff, 0x22011002, mmSPI_RESOURCE_RESERVE_CU_0, 0xffffffff, 0x00000800, mmSPI_RESOURCE_RESERVE_CU_1, 0xffffffff, 0x00000800, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c index 063f08a9957a..31d99b0010f7 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c @@ -109,10 +109,12 @@ static const u32 fiji_mgcg_cgcg_init[] = static const u32 golden_settings_polaris11_a11[] = { mmSDMA0_CHICKEN_BITS, 0xfc910007, 0x00810007, + mmSDMA0_CLK_CTRL, 0xff000fff, 0x00000000, mmSDMA0_GFX_IB_CNTL, 0x800f0111, 0x00000100, mmSDMA0_RLC0_IB_CNTL, 0x800f0111, 0x00000100, mmSDMA0_RLC1_IB_CNTL, 0x800f0111, 0x00000100, mmSDMA1_CHICKEN_BITS, 0xfc910007, 0x00810007, + mmSDMA1_CLK_CTRL, 0xff000fff, 0x00000000, mmSDMA1_GFX_IB_CNTL, 0x800f0111, 0x00000100, mmSDMA1_RLC0_IB_CNTL, 0x800f0111, 0x00000100, mmSDMA1_RLC1_IB_CNTL, 0x800f0111, 0x00000100, From 3b59c344ab6e2d00b0f4ad946024572618c87502 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Fri, 13 May 2016 17:07:59 -0400 Subject: [PATCH 172/393] drm/amd/powerplay: fix bugs of checking if dpm is running on Tonga Fixes OD failures on Tonga. Reviewed-by: Alex Deucher Signed-off-by: Eric Huang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c index cb2833527404..7c3f82b29402 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c @@ -5331,7 +5331,7 @@ static int tonga_freeze_sclk_mclk_dpm(struct pp_hwmgr *hwmgr) (data->need_update_smu7_dpm_table & (DPMTABLE_OD_UPDATE_SCLK + DPMTABLE_UPDATE_SCLK))) { PP_ASSERT_WITH_CODE( - true == tonga_is_dpm_running(hwmgr), + 0 == tonga_is_dpm_running(hwmgr), "Trying to freeze SCLK DPM when DPM is disabled", ); PP_ASSERT_WITH_CODE( @@ -5344,7 +5344,7 @@ static int tonga_freeze_sclk_mclk_dpm(struct pp_hwmgr *hwmgr) if ((0 == data->mclk_dpm_key_disabled) && (data->need_update_smu7_dpm_table & DPMTABLE_OD_UPDATE_MCLK)) { - PP_ASSERT_WITH_CODE(true == tonga_is_dpm_running(hwmgr), + PP_ASSERT_WITH_CODE(0 == tonga_is_dpm_running(hwmgr), "Trying to freeze MCLK DPM when DPM is disabled", ); PP_ASSERT_WITH_CODE( @@ -5647,7 +5647,7 @@ static int tonga_unfreeze_sclk_mclk_dpm(struct pp_hwmgr *hwmgr) (data->need_update_smu7_dpm_table & (DPMTABLE_OD_UPDATE_SCLK + DPMTABLE_UPDATE_SCLK))) { - PP_ASSERT_WITH_CODE(true == tonga_is_dpm_running(hwmgr), + PP_ASSERT_WITH_CODE(0 == tonga_is_dpm_running(hwmgr), "Trying to Unfreeze SCLK DPM when DPM is disabled", ); PP_ASSERT_WITH_CODE( @@ -5661,7 +5661,7 @@ static int tonga_unfreeze_sclk_mclk_dpm(struct pp_hwmgr *hwmgr) (data->need_update_smu7_dpm_table & DPMTABLE_OD_UPDATE_MCLK)) { PP_ASSERT_WITH_CODE( - true == tonga_is_dpm_running(hwmgr), + 0 == tonga_is_dpm_running(hwmgr), "Trying to Unfreeze MCLK DPM when DPM is disabled", ); PP_ASSERT_WITH_CODE( From 45c04704e467fffe3525205454d9627325dae308 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 18 May 2016 16:19:01 +0300 Subject: [PATCH 173/393] ASoC: twl6040: Disconnect AUX output pads on digital mute Disconnect also the path to AUXL from the HF path during digital_mute to avoid pop noise leakage to Line-out pads. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- include/linux/mfd/twl6040.h | 1 + sound/soc/codecs/twl6040.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index 8f9fc3d26e6d..8e95cd87cd74 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -134,6 +134,7 @@ #define TWL6040_HFDACENA (1 << 0) #define TWL6040_HFPGAENA (1 << 1) #define TWL6040_HFDRVENA (1 << 4) +#define TWL6040_HFSWENA (1 << 6) /* VIBCTLL/R (0x18/0x1A) fields */ diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index bc3de2e844e6..d1e3a932cbf3 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -983,9 +983,9 @@ static void twl6040_mute_path(struct snd_soc_codec *codec, enum twl6040_dai_id i if (mute) { /* Power down drivers and DACs */ hflctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA | - TWL6040_HFDRVENA); + TWL6040_HFDRVENA | TWL6040_HFSWENA); hfrctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA | - TWL6040_HFDRVENA); + TWL6040_HFDRVENA | TWL6040_HFSWENA); } twl6040_reg_write(twl6040, TWL6040_REG_HFLCTL, hflctl); From d3030d11961a8c103cf07aed59905276ddfc06c2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 18 May 2016 18:30:39 +0100 Subject: [PATCH 174/393] ASoC: ak4642: Enable cache usage to fix crashes on resume The ak4642 driver is using a regmap cache sync to restore the configuration of the chip on resume but (as Peter observed) does not actually define a register cache which means that the resume is never going to work and we trigger asserts in regmap. Fix this by enabling caching. Reported-by: Geert Uytterhoeven Reported-by: Peter Ujfalusi Tested-by: Geert Uytterhoeven Signed-off-by: Mark Brown Cc: stable@vger.kernel.org --- sound/soc/codecs/ak4642.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 1ee8506c06c7..4d8b9e49e8d6 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -560,6 +560,7 @@ static const struct regmap_config ak4642_regmap = { .max_register = FIL1_3, .reg_defaults = ak4642_reg, .num_reg_defaults = NUM_AK4642_REG_DEFAULTS, + .cache_type = REGCACHE_RBTREE, }; static const struct regmap_config ak4643_regmap = { @@ -568,6 +569,7 @@ static const struct regmap_config ak4643_regmap = { .max_register = SPK_MS, .reg_defaults = ak4643_reg, .num_reg_defaults = ARRAY_SIZE(ak4643_reg), + .cache_type = REGCACHE_RBTREE, }; static const struct regmap_config ak4648_regmap = { @@ -576,6 +578,7 @@ static const struct regmap_config ak4648_regmap = { .max_register = EQ_FBEQE, .reg_defaults = ak4648_reg, .num_reg_defaults = ARRAY_SIZE(ak4648_reg), + .cache_type = REGCACHE_RBTREE, }; static const struct ak4642_drvdata ak4642_drvdata = { From 761de5f9da8306c80c75883e0cbaa78f0244c1a0 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Thu, 19 May 2016 16:45:27 +0530 Subject: [PATCH 175/393] drm/amd/powerplay/hwmgr: use kmemdup Use kmemdup when some other buffer is immediately copied into allocated region. It replaces call to allocation followed by memcpy, by a single call to kmemdup. Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c index 6f1bad41381a..24a16e49b571 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/fiji_hwmgr.c @@ -5074,11 +5074,11 @@ static int fiji_get_pp_table(struct pp_hwmgr *hwmgr, char **table) struct fiji_hwmgr *data = (struct fiji_hwmgr *)(hwmgr->backend); if (!data->soft_pp_table) { - data->soft_pp_table = kzalloc(hwmgr->soft_pp_table_size, GFP_KERNEL); + data->soft_pp_table = kmemdup(hwmgr->soft_pp_table, + hwmgr->soft_pp_table_size, + GFP_KERNEL); if (!data->soft_pp_table) return -ENOMEM; - memcpy(data->soft_pp_table, hwmgr->soft_pp_table, - hwmgr->soft_pp_table_size); } *table = (char *)&data->soft_pp_table; From 510e65dfb0e1817ef48c43839b8789131800a6f6 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Thu, 19 May 2016 16:45:29 +0530 Subject: [PATCH 176/393] drm/amd/powerplay/hwmgr: use kmemdup Use kmemdup when some other buffer is immediately copied into allocated region. It replaces call to allocation followed by memcpy, by a single call to kmemdup. Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c index 7c3f82b29402..16fed487973b 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/tonga_hwmgr.c @@ -6056,11 +6056,11 @@ static int tonga_get_pp_table(struct pp_hwmgr *hwmgr, char **table) struct tonga_hwmgr *data = (struct tonga_hwmgr *)(hwmgr->backend); if (!data->soft_pp_table) { - data->soft_pp_table = kzalloc(hwmgr->soft_pp_table_size, GFP_KERNEL); + data->soft_pp_table = kmemdup(hwmgr->soft_pp_table, + hwmgr->soft_pp_table_size, + GFP_KERNEL); if (!data->soft_pp_table) return -ENOMEM; - memcpy(data->soft_pp_table, hwmgr->soft_pp_table, - hwmgr->soft_pp_table_size); } *table = (char *)&data->soft_pp_table; From c688c641c514933aa82c9707c99a6fff8831e103 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Thu, 19 May 2016 16:45:28 +0530 Subject: [PATCH 177/393] drm/amd/powerplay/hwmgr: use kmemdup Use kmemdup when some other buffer is immediately copied into allocated region. It replaces call to allocation followed by memcpy, by a single call to kmemdup. Signed-off-by: Muhammad Falak R Wani Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c index 4cef7a527696..aa6be033f21b 100644 --- a/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c +++ b/drivers/gpu/drm/amd/powerplay/hwmgr/polaris10_hwmgr.c @@ -4726,11 +4726,11 @@ static int polaris10_get_pp_table(struct pp_hwmgr *hwmgr, char **table) struct polaris10_hwmgr *data = (struct polaris10_hwmgr *)(hwmgr->backend); if (!data->soft_pp_table) { - data->soft_pp_table = kzalloc(hwmgr->soft_pp_table_size, GFP_KERNEL); + data->soft_pp_table = kmemdup(hwmgr->soft_pp_table, + hwmgr->soft_pp_table_size, + GFP_KERNEL); if (!data->soft_pp_table) return -ENOMEM; - memcpy(data->soft_pp_table, hwmgr->soft_pp_table, - hwmgr->soft_pp_table_size); } *table = (char *)&data->soft_pp_table; From 423cd785619ac6778252fbdb916505aa1c153959 Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Fri, 20 May 2016 15:47:23 +0800 Subject: [PATCH 178/393] ALSA: hda - Fix headphone noise on Dell XPS 13 9360 The headphone has noise when playing sound or switching microphone sources. It uses the same codec on XPS 13 9350, but with different subsystem ID. Applying the fixup can solve the issue. Also, changing the model name to better differentiate models. v2: Reorder by device ID. Signed-off-by: Kai-Heng Feng Cc: Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 002f153bc659..10ca90058bfa 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5466,8 +5466,9 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x06de, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), SND_PCI_QUIRK(0x1028, 0x06df, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), SND_PCI_QUIRK(0x1028, 0x06e0, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), - SND_PCI_QUIRK(0x1028, 0x0704, "Dell XPS 13", ALC256_FIXUP_DELL_XPS_13_HEADPHONE_NOISE), + SND_PCI_QUIRK(0x1028, 0x0704, "Dell XPS 13 9350", ALC256_FIXUP_DELL_XPS_13_HEADPHONE_NOISE), SND_PCI_QUIRK(0x1028, 0x0725, "Dell Inspiron 3162", ALC255_FIXUP_DELL_SPK_NOISE), + SND_PCI_QUIRK(0x1028, 0x075b, "Dell XPS 13 9360", ALC256_FIXUP_DELL_XPS_13_HEADPHONE_NOISE), SND_PCI_QUIRK(0x1028, 0x164a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), From affa80bd97f7ca282d1faa91667b3ee9e4c590e6 Mon Sep 17 00:00:00 2001 From: Ricky Liang Date: Fri, 20 May 2016 10:58:59 -0700 Subject: [PATCH 179/393] Input: uinput - handle compat ioctl for UI_SET_PHYS When running a 32-bit userspace on a 64-bit kernel, the UI_SET_PHYS ioctl needs to be treated with special care, as it has the pointer size encoded in the command. Signed-off-by: Ricky Liang Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/misc/uinput.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c index 4eb9e4d94f46..79338f4bdecb 100644 --- a/drivers/input/misc/uinput.c +++ b/drivers/input/misc/uinput.c @@ -981,9 +981,15 @@ static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg) } #ifdef CONFIG_COMPAT + +#define UI_SET_PHYS_COMPAT _IOW(UINPUT_IOCTL_BASE, 108, compat_uptr_t) + static long uinput_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + if (cmd == UI_SET_PHYS_COMPAT) + cmd = UI_SET_PHYS; + return uinput_ioctl_handler(file, cmd, arg, compat_ptr(arg)); } #endif From 6de5a8a5001687df720b36729c799991cc0faff5 Mon Sep 17 00:00:00 2001 From: Eli Cooper Date: Sun, 20 Mar 2016 00:58:39 +0800 Subject: [PATCH 180/393] um: fix FPU state preservation around signal handlers This patch makes UML saves/restores FPU state from/to the fpstate in pt_regs when setting up or returning from a signal stack, rather than calling ptrace directly. This ensures that FPU state is correctly preserved around signal handlers in a multi-threaded scenario. Signed-off-by: Eli Cooper --- arch/x86/um/signal.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/arch/x86/um/signal.c b/arch/x86/um/signal.c index 14fcd01ed992..dac23ee4e5a1 100644 --- a/arch/x86/um/signal.c +++ b/arch/x86/um/signal.c @@ -225,20 +225,10 @@ static int copy_sc_from_user(struct pt_regs *regs, } else #endif { - struct user_i387_struct fp; - - err = copy_from_user(&fp, (void *)sc.fpstate, + err = copy_from_user(regs->regs.fp, (void *)sc.fpstate, sizeof(struct user_i387_struct)); if (err) return 1; - - err = restore_fp_registers(pid, (unsigned long *) &fp); - if (err < 0) { - printk(KERN_ERR "copy_sc_from_user - " - "restore_fp_registers failed, errno = %d\n", - -err); - return 1; - } } return 0; } @@ -325,10 +315,8 @@ static int copy_sc_to_user(struct sigcontext __user *to, } else #endif { - struct user_i387_struct fp; - - err = save_fp_registers(pid, (unsigned long *) &fp); - if (copy_to_user(to_fp, &fp, sizeof(struct user_i387_struct))) + if (copy_to_user(to_fp, regs->regs.fp, + sizeof(struct user_i387_struct))) return 1; } From b6024b21fec8367ef961a771cc9dde31f1831965 Mon Sep 17 00:00:00 2001 From: Eli Cooper Date: Sun, 20 Mar 2016 00:58:40 +0800 Subject: [PATCH 181/393] um: extend fpstate to _xstate to support YMM registers Extends fpstate to _xstate, in order to hold AVX/YMM registers. To avoid oversized stack frame, the following functions have been refactored by using malloc. - sig_handler_common - timer_real_alarm_handler Signed-off-by: Eli Cooper --- arch/um/os-Linux/signal.c | 28 ++++++++++++++++++++-------- arch/x86/um/signal.c | 23 +++++++++++------------ arch/x86/um/user-offsets.c | 2 +- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c index 7801666514ed..8acaf4e384c0 100644 --- a/arch/um/os-Linux/signal.c +++ b/arch/um/os-Linux/signal.c @@ -29,23 +29,29 @@ void (*sig_info[NSIG])(int, struct siginfo *, struct uml_pt_regs *) = { static void sig_handler_common(int sig, struct siginfo *si, mcontext_t *mc) { - struct uml_pt_regs r; + struct uml_pt_regs *r; int save_errno = errno; - r.is_user = 0; + r = malloc(sizeof(struct uml_pt_regs)); + if (!r) + panic("out of memory"); + + r->is_user = 0; if (sig == SIGSEGV) { /* For segfaults, we want the data from the sigcontext. */ - get_regs_from_mc(&r, mc); - GET_FAULTINFO_FROM_MC(r.faultinfo, mc); + get_regs_from_mc(r, mc); + GET_FAULTINFO_FROM_MC(r->faultinfo, mc); } /* enable signals if sig isn't IRQ signal */ if ((sig != SIGIO) && (sig != SIGWINCH) && (sig != SIGALRM)) unblock_signals(); - (*sig_info[sig])(sig, si, &r); + (*sig_info[sig])(sig, si, r); errno = save_errno; + + free(r); } /* @@ -83,11 +89,17 @@ void sig_handler(int sig, struct siginfo *si, mcontext_t *mc) static void timer_real_alarm_handler(mcontext_t *mc) { - struct uml_pt_regs regs; + struct uml_pt_regs *regs; + + regs = malloc(sizeof(struct uml_pt_regs)); + if (!regs) + panic("out of memory"); if (mc != NULL) - get_regs_from_mc(®s, mc); - timer_handler(SIGALRM, NULL, ®s); + get_regs_from_mc(regs, mc); + timer_handler(SIGALRM, NULL, regs); + + free(regs); } void timer_alarm_handler(int sig, struct siginfo *unused_si, mcontext_t *mc) diff --git a/arch/x86/um/signal.c b/arch/x86/um/signal.c index dac23ee4e5a1..49e503697022 100644 --- a/arch/x86/um/signal.c +++ b/arch/x86/um/signal.c @@ -226,7 +226,7 @@ static int copy_sc_from_user(struct pt_regs *regs, #endif { err = copy_from_user(regs->regs.fp, (void *)sc.fpstate, - sizeof(struct user_i387_struct)); + sizeof(struct _xstate)); if (err) return 1; } @@ -234,7 +234,7 @@ static int copy_sc_from_user(struct pt_regs *regs, } static int copy_sc_to_user(struct sigcontext __user *to, - struct _fpstate __user *to_fp, struct pt_regs *regs, + struct _xstate __user *to_fp, struct pt_regs *regs, unsigned long mask) { struct sigcontext sc; @@ -300,23 +300,22 @@ static int copy_sc_to_user(struct sigcontext __user *to, return 1; } - err = convert_fxsr_to_user(to_fp, &fpx); + err = convert_fxsr_to_user(&to_fp->fpstate, &fpx); if (err) return 1; - err |= __put_user(fpx.swd, &to_fp->status); - err |= __put_user(X86_FXSR_MAGIC, &to_fp->magic); + err |= __put_user(fpx.swd, &to_fp->fpstate.status); + err |= __put_user(X86_FXSR_MAGIC, &to_fp->fpstate.magic); if (err) return 1; - if (copy_to_user(&to_fp->_fxsr_env[0], &fpx, + if (copy_to_user(&to_fp->fpstate._fxsr_env[0], &fpx, sizeof(struct user_fxsr_struct))) return 1; } else #endif { - if (copy_to_user(to_fp, regs->regs.fp, - sizeof(struct user_i387_struct))) + if (copy_to_user(to_fp, regs->regs.fp, sizeof(struct _xstate))) return 1; } @@ -325,7 +324,7 @@ static int copy_sc_to_user(struct sigcontext __user *to, #ifdef CONFIG_X86_32 static int copy_ucontext_to_user(struct ucontext __user *uc, - struct _fpstate __user *fp, sigset_t *set, + struct _xstate __user *fp, sigset_t *set, unsigned long sp) { int err = 0; @@ -341,7 +340,7 @@ struct sigframe char __user *pretcode; int sig; struct sigcontext sc; - struct _fpstate fpstate; + struct _xstate fpstate; unsigned long extramask[_NSIG_WORDS-1]; char retcode[8]; }; @@ -354,7 +353,7 @@ struct rt_sigframe void __user *puc; struct siginfo info; struct ucontext uc; - struct _fpstate fpstate; + struct _xstate fpstate; char retcode[8]; }; @@ -483,7 +482,7 @@ struct rt_sigframe char __user *pretcode; struct ucontext uc; struct siginfo info; - struct _fpstate fpstate; + struct _xstate fpstate; }; int setup_signal_stack_si(unsigned long stack_top, struct ksignal *ksig, diff --git a/arch/x86/um/user-offsets.c b/arch/x86/um/user-offsets.c index 470564bbd08e..cb3c22370cf5 100644 --- a/arch/x86/um/user-offsets.c +++ b/arch/x86/um/user-offsets.c @@ -50,7 +50,7 @@ void foo(void) DEFINE(HOST_GS, GS); DEFINE(HOST_ORIG_AX, ORIG_EAX); #else - DEFINE(HOST_FP_SIZE, sizeof(struct _fpstate) / sizeof(unsigned long)); + DEFINE(HOST_FP_SIZE, sizeof(struct _xstate) / sizeof(unsigned long)); DEFINE_LONGS(HOST_BX, RBX); DEFINE_LONGS(HOST_CX, RCX); DEFINE_LONGS(HOST_DI, RDI); From a78ff1112263fdd871d3506dbcff44f6f12e8423 Mon Sep 17 00:00:00 2001 From: Eli Cooper Date: Sun, 20 Mar 2016 00:58:41 +0800 Subject: [PATCH 182/393] um: add extended processor state save/restore support This patch extends save_fp_registers() and restore_fp_registers() to use PTRACE_GETREGSET and PTRACE_SETREGSET with the XSTATE note type, adding support for new processor state extensions between context switches. When the new ptrace requests are unavailable, it falls back to the old PTRACE_GETFPREGS and PTRACE_SETFPREGS methods, which have been renamed to save_i387_registers() and restore_i387_registers(). Now these functions expect *fp_regs to have the space of an _xstate struct. Thus, this also makes ptrace in UML responde to PTRACE_GETFPREGS/_SETFPREG requests with a user_i387_struct (thus independent from HOST_FP_SIZE), and by calling save_i387_registers() and restore_i387_registers() instead of the extended save_fp_registers() and restore_fp_registers() functions. Signed-off-by: Eli Cooper --- arch/um/include/shared/registers.h | 2 ++ arch/um/kernel/process.c | 2 +- arch/x86/um/os-Linux/registers.c | 49 +++++++++++++++++++++++++-- arch/x86/um/ptrace_32.c | 5 +-- arch/x86/um/ptrace_64.c | 16 ++++----- arch/x86/um/shared/sysdep/ptrace_64.h | 4 +-- 6 files changed, 62 insertions(+), 16 deletions(-) diff --git a/arch/um/include/shared/registers.h b/arch/um/include/shared/registers.h index f5b76355ad71..a74449b5b0e3 100644 --- a/arch/um/include/shared/registers.h +++ b/arch/um/include/shared/registers.h @@ -9,6 +9,8 @@ #include #include +extern int save_i387_registers(int pid, unsigned long *fp_regs); +extern int restore_i387_registers(int pid, unsigned long *fp_regs); extern int save_fp_registers(int pid, unsigned long *fp_regs); extern int restore_fp_registers(int pid, unsigned long *fp_regs); extern int save_fpx_registers(int pid, unsigned long *fp_regs); diff --git a/arch/um/kernel/process.c b/arch/um/kernel/process.c index 48af59aae129..d55a473dca4a 100644 --- a/arch/um/kernel/process.c +++ b/arch/um/kernel/process.c @@ -402,6 +402,6 @@ int elf_core_copy_fpregs(struct task_struct *t, elf_fpregset_t *fpu) { int cpu = current_thread_info()->cpu; - return save_fp_registers(userspace_pid[cpu], (unsigned long *) fpu); + return save_i387_registers(userspace_pid[cpu], (unsigned long *) fpu); } diff --git a/arch/x86/um/os-Linux/registers.c b/arch/x86/um/os-Linux/registers.c index 41bfe84e11ab..00f54a91bb4b 100644 --- a/arch/x86/um/os-Linux/registers.c +++ b/arch/x86/um/os-Linux/registers.c @@ -11,21 +11,56 @@ #endif #include #include +#include +#include +#include -int save_fp_registers(int pid, unsigned long *fp_regs) +int have_xstate_support; + +int save_i387_registers(int pid, unsigned long *fp_regs) { if (ptrace(PTRACE_GETFPREGS, pid, 0, fp_regs) < 0) return -errno; return 0; } -int restore_fp_registers(int pid, unsigned long *fp_regs) +int save_fp_registers(int pid, unsigned long *fp_regs) +{ + struct iovec iov; + + if (have_xstate_support) { + iov.iov_base = fp_regs; + iov.iov_len = sizeof(struct _xstate); + if (ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov) < 0) + return -errno; + return 0; + } else { + return save_i387_registers(pid, fp_regs); + } +} + +int restore_i387_registers(int pid, unsigned long *fp_regs) { if (ptrace(PTRACE_SETFPREGS, pid, 0, fp_regs) < 0) return -errno; return 0; } +int restore_fp_registers(int pid, unsigned long *fp_regs) +{ + struct iovec iov; + + if (have_xstate_support) { + iov.iov_base = fp_regs; + iov.iov_len = sizeof(struct _xstate); + if (ptrace(PTRACE_SETREGSET, pid, NT_X86_XSTATE, &iov) < 0) + return -errno; + return 0; + } else { + return restore_i387_registers(pid, fp_regs); + } +} + #ifdef __i386__ int have_fpx_regs = 1; int save_fpx_registers(int pid, unsigned long *fp_regs) @@ -85,6 +120,16 @@ int put_fp_registers(int pid, unsigned long *regs) return restore_fp_registers(pid, regs); } +void arch_init_registers(int pid) +{ + struct _xstate fp_regs; + struct iovec iov; + + iov.iov_base = &fp_regs; + iov.iov_len = sizeof(struct _xstate); + if (ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov) == 0) + have_xstate_support = 1; +} #endif unsigned long get_thread_reg(int reg, jmp_buf *buf) diff --git a/arch/x86/um/ptrace_32.c b/arch/x86/um/ptrace_32.c index 47c78d5e5c32..ebd4dd6ef73b 100644 --- a/arch/x86/um/ptrace_32.c +++ b/arch/x86/um/ptrace_32.c @@ -194,7 +194,8 @@ static int get_fpregs(struct user_i387_struct __user *buf, struct task_struct *c int err, n, cpu = ((struct thread_info *) child->stack)->cpu; struct user_i387_struct fpregs; - err = save_fp_registers(userspace_pid[cpu], (unsigned long *) &fpregs); + err = save_i387_registers(userspace_pid[cpu], + (unsigned long *) &fpregs); if (err) return err; @@ -214,7 +215,7 @@ static int set_fpregs(struct user_i387_struct __user *buf, struct task_struct *c if (n > 0) return -EFAULT; - return restore_fp_registers(userspace_pid[cpu], + return restore_i387_registers(userspace_pid[cpu], (unsigned long *) &fpregs); } diff --git a/arch/x86/um/ptrace_64.c b/arch/x86/um/ptrace_64.c index a629694ee750..faab418876ce 100644 --- a/arch/x86/um/ptrace_64.c +++ b/arch/x86/um/ptrace_64.c @@ -222,14 +222,14 @@ int is_syscall(unsigned long addr) static int get_fpregs(struct user_i387_struct __user *buf, struct task_struct *child) { int err, n, cpu = ((struct thread_info *) child->stack)->cpu; - long fpregs[HOST_FP_SIZE]; + struct user_i387_struct fpregs; - BUG_ON(sizeof(*buf) != sizeof(fpregs)); - err = save_fp_registers(userspace_pid[cpu], fpregs); + err = save_i387_registers(userspace_pid[cpu], + (unsigned long *) &fpregs); if (err) return err; - n = copy_to_user(buf, fpregs, sizeof(fpregs)); + n = copy_to_user(buf, &fpregs, sizeof(fpregs)); if (n > 0) return -EFAULT; @@ -239,14 +239,14 @@ static int get_fpregs(struct user_i387_struct __user *buf, struct task_struct *c static int set_fpregs(struct user_i387_struct __user *buf, struct task_struct *child) { int n, cpu = ((struct thread_info *) child->stack)->cpu; - long fpregs[HOST_FP_SIZE]; + struct user_i387_struct fpregs; - BUG_ON(sizeof(*buf) != sizeof(fpregs)); - n = copy_from_user(fpregs, buf, sizeof(fpregs)); + n = copy_from_user(&fpregs, buf, sizeof(fpregs)); if (n > 0) return -EFAULT; - return restore_fp_registers(userspace_pid[cpu], fpregs); + return restore_i387_registers(userspace_pid[cpu], + (unsigned long *) &fpregs); } long subarch_ptrace(struct task_struct *child, long request, diff --git a/arch/x86/um/shared/sysdep/ptrace_64.h b/arch/x86/um/shared/sysdep/ptrace_64.h index 919789f1071e..0dc223aa1c2d 100644 --- a/arch/x86/um/shared/sysdep/ptrace_64.h +++ b/arch/x86/um/shared/sysdep/ptrace_64.h @@ -57,8 +57,6 @@ #define UPT_SYSCALL_ARG5(r) UPT_R8(r) #define UPT_SYSCALL_ARG6(r) UPT_R9(r) -static inline void arch_init_registers(int pid) -{ -} +extern void arch_init_registers(int pid); #endif From b3daa5ef52c26acd7432c787989bd92d48070c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Fri, 6 May 2016 16:46:52 +0300 Subject: [PATCH 183/393] drm: Add helper for DP++ adaptors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a helper which aids in the identification of DP dual mode (aka. DP++) adaptors. There are several types of adaptors specified: type 1 DVI, type 1 HDMI, type 2 DVI, type 2 HDMI Type 1 adaptors have a max TMDS clock limit of 165MHz, type 2 adaptors may go as high as 300MHz and they provide a register informing the source device what the actual limit is. Supposedly also type 1 adaptors may optionally implement this register. This TMDS clock limit is the main reason why we need to identify these adaptors. Type 1 adaptors provide access to their internal registers and the sink DDC bus through I2C. Type 2 adaptors provide this access both via I2C and I2C-over-AUX. A type 2 source device may choose to implement either of these methods. If a source device implements the I2C-over-AUX method, then the driver will obviously need specific support for such adaptors since the port is driven like an HDMI port, but DDC communication happes over the AUX channel. This helper should be enough to identify the adaptor type (some type 1 DVI adaptors may be a slight exception) and the maximum TMDS clock limit. Another feature that may be available is control over the TMDS output buffers on the adaptor, possibly allowing for some power saving when the TMDS link is down. Other user controllable features that may be available in the adaptors are downstream i2c bus speed control when using i2c-over-aux, and some control over the CEC pin. I chose not to provide any helper functions for those since I have no use for them in i915 at this time. The rest of the registers in the adaptor are mostly just information, eg. IEEE OUI, hardware and firmware revision, etc. v2: Pass adaptor type to helper functions to ease driver implementation Fix a bunch of typoes (Paulo) Add DRM_DP_DUAL_MODE_UNKNOWN for the case where we don't (yet) know the type (Paulo) Reject 0x00 and 0xff DP_DUAL_MODE_MAX_TMDS_CLOCK values (Paulo) Adjust drm_dp_dual_mode_detect() type2 vs. type1 detection to ease future LSPCON enabling Remove the unused DP_DUAL_MODE_LAST_RESERVED define v3: Fix kernel doc function argument descriptions (Jani) s/NONE/UNKNOWN/ in drm_dp_dual_mode_detect() docs Add kernel doc for enum drm_dp_dual_mode_type Actually build the docs Fix more typoes v4: Adjust code indentation of type2 adaptor detection (Shashank) Add debug messages for failurs cases (Shashank) v5: EXPORT_SYMBOL(drm_dp_dual_mode_read) (Paulo) Cc: stable@vger.kernel.org Cc: Tore Anderson Cc: Paulo Zanoni Cc: Shashank Sharma Cc: Daniel Vetter Cc: Shashank Sharma Signed-off-by: Ville Syrjälä Reviewed-by: Shashank Sharma (v4) Link: http://patchwork.freedesktop.org/patch/msgid/1462542412-25533-1-git-send-email-ville.syrjala@linux.intel.com (cherry picked from commit ede53344dbfd1dd43bfd73eb6af743d37c56a7c3) Signed-off-by: Jani Nikula --- Documentation/DocBook/gpu.tmpl | 6 + drivers/gpu/drm/Makefile | 2 +- drivers/gpu/drm/drm_dp_dual_mode_helper.c | 366 ++++++++++++++++++++++ include/drm/drm_dp_dual_mode_helper.h | 92 ++++++ 4 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_dp_dual_mode_helper.c create mode 100644 include/drm/drm_dp_dual_mode_helper.h diff --git a/Documentation/DocBook/gpu.tmpl b/Documentation/DocBook/gpu.tmpl index 1464fb2f3c46..c248124357df 100644 --- a/Documentation/DocBook/gpu.tmpl +++ b/Documentation/DocBook/gpu.tmpl @@ -1621,6 +1621,12 @@ void intel_crt_init(struct drm_device *dev) !Pdrivers/gpu/drm/drm_dp_helper.c dp helpers !Iinclude/drm/drm_dp_helper.h !Edrivers/gpu/drm/drm_dp_helper.c + + + Display Port Dual Mode Adaptor Helper Functions Reference +!Pdrivers/gpu/drm/drm_dp_dual_mode_helper.c dp dual mode helpers +!Iinclude/drm/drm_dp_dual_mode_helper.h +!Edrivers/gpu/drm/drm_dp_dual_mode_helper.c Display Port MST Helper Functions Reference diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 6eb94fc561dc..22228ef50f36 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -23,7 +23,7 @@ drm-$(CONFIG_AGP) += drm_agpsupport.o drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_probe_helper.o \ drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \ - drm_kms_helper_common.o + drm_kms_helper_common.o drm_dp_dual_mode_helper.o drm_kms_helper-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o diff --git a/drivers/gpu/drm/drm_dp_dual_mode_helper.c b/drivers/gpu/drm/drm_dp_dual_mode_helper.c new file mode 100644 index 000000000000..a7b2a751f6fe --- /dev/null +++ b/drivers/gpu/drm/drm_dp_dual_mode_helper.c @@ -0,0 +1,366 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * DOC: dp dual mode helpers + * + * Helper functions to deal with DP dual mode (aka. DP++) adaptors. + * + * Type 1: + * Adaptor registers (if any) and the sink DDC bus may be accessed via I2C. + * + * Type 2: + * Adaptor registers and sink DDC bus can be accessed either via I2C or + * I2C-over-AUX. Source devices may choose to implement either of these + * access methods. + */ + +#define DP_DUAL_MODE_SLAVE_ADDRESS 0x40 + +/** + * drm_dp_dual_mode_read - Read from the DP dual mode adaptor register(s) + * @adapter: I2C adapter for the DDC bus + * @offset: register offset + * @buffer: buffer for return data + * @size: sizo of the buffer + * + * Reads @size bytes from the DP dual mode adaptor registers + * starting at @offset. + * + * Returns: + * 0 on success, negative error code on failure + */ +ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter, + u8 offset, void *buffer, size_t size) +{ + struct i2c_msg msgs[] = { + { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = 0, + .len = 1, + .buf = &offset, + }, + { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = I2C_M_RD, + .len = size, + .buf = buffer, + }, + }; + int ret; + + ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(msgs)) + return -EPROTO; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_read); + +/** + * drm_dp_dual_mode_write - Write to the DP dual mode adaptor register(s) + * @adapter: I2C adapter for the DDC bus + * @offset: register offset + * @buffer: buffer for write data + * @size: sizo of the buffer + * + * Writes @size bytes to the DP dual mode adaptor registers + * starting at @offset. + * + * Returns: + * 0 on success, negative error code on failure + */ +ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter, + u8 offset, const void *buffer, size_t size) +{ + struct i2c_msg msg = { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = 0, + .len = 1 + size, + .buf = NULL, + }; + void *data; + int ret; + + data = kmalloc(msg.len, GFP_TEMPORARY); + if (!data) + return -ENOMEM; + + msg.buf = data; + + memcpy(data, &offset, 1); + memcpy(data + 1, buffer, size); + + ret = i2c_transfer(adapter, &msg, 1); + + kfree(data); + + if (ret < 0) + return ret; + if (ret != 1) + return -EPROTO; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_write); + +static bool is_hdmi_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN]) +{ + static const char dp_dual_mode_hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = + "DP-HDMI ADAPTOR\x04"; + + return memcmp(hdmi_id, dp_dual_mode_hdmi_id, + sizeof(dp_dual_mode_hdmi_id)) == 0; +} + +static bool is_type2_adaptor(uint8_t adaptor_id) +{ + return adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | + DP_DUAL_MODE_REV_TYPE2); +} + +/** + * drm_dp_dual_mode_detect - Identify the DP dual mode adaptor + * @adapter: I2C adapter for the DDC bus + * + * Attempt to identify the type of the DP dual mode adaptor used. + * + * Note that when the answer is @DRM_DP_DUAL_MODE_UNKNOWN it's not + * certain whether we're dealing with a native HDMI port or + * a type 1 DVI dual mode adaptor. The driver will have to use + * some other hardware/driver specific mechanism to make that + * distinction. + * + * Returns: + * The type of the DP dual mode adaptor used + */ +enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(struct i2c_adapter *adapter) +{ + char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = {}; + uint8_t adaptor_id = 0x00; + ssize_t ret; + + /* + * Let's see if the adaptor is there the by reading the + * HDMI ID registers. + * + * Note that type 1 DVI adaptors are not required to implemnt + * any registers, and that presents a problem for detection. + * If the i2c transfer is nacked, we may or may not be dealing + * with a type 1 DVI adaptor. Some other mechanism of detecting + * the presence of the adaptor is required. One way would be + * to check the state of the CONFIG1 pin, Another method would + * simply require the driver to know whether the port is a DP++ + * port or a native HDMI port. Both of these methods are entirely + * hardware/driver specific so we can't deal with them here. + */ + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_HDMI_ID, + hdmi_id, sizeof(hdmi_id)); + if (ret) + return DRM_DP_DUAL_MODE_UNKNOWN; + + /* + * Sigh. Some (maybe all?) type 1 adaptors are broken and ack + * the offset but ignore it, and instead they just always return + * data from the start of the HDMI ID buffer. So for a broken + * type 1 HDMI adaptor a single byte read will always give us + * 0x44, and for a type 1 DVI adaptor it should give 0x00 + * (assuming it implements any registers). Fortunately neither + * of those values will match the type 2 signature of the + * DP_DUAL_MODE_ADAPTOR_ID register so we can proceed with + * the type 2 adaptor detection safely even in the presence + * of broken type 1 adaptors. + */ + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_ADAPTOR_ID, + &adaptor_id, sizeof(adaptor_id)); + if (ret == 0) { + if (is_type2_adaptor(adaptor_id)) { + if (is_hdmi_adaptor(hdmi_id)) + return DRM_DP_DUAL_MODE_TYPE2_HDMI; + else + return DRM_DP_DUAL_MODE_TYPE2_DVI; + } + } + + if (is_hdmi_adaptor(hdmi_id)) + return DRM_DP_DUAL_MODE_TYPE1_HDMI; + else + return DRM_DP_DUAL_MODE_TYPE1_DVI; +} +EXPORT_SYMBOL(drm_dp_dual_mode_detect); + +/** + * drm_dp_dual_mode_max_tmds_clock - Max TMDS clock for DP dual mode adaptor + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * + * Determine the max TMDS clock the adaptor supports based on the + * type of the dual mode adaptor and the DP_DUAL_MODE_MAX_TMDS_CLOCK + * register (on type2 adaptors). As some type 1 adaptors have + * problems with registers (see comments in drm_dp_dual_mode_detect()) + * we don't read the register on those, instead we simply assume + * a 165 MHz limit based on the specification. + * + * Returns: + * Maximum supported TMDS clock rate for the DP dual mode adaptor in kHz. + */ +int drm_dp_dual_mode_max_tmds_clock(enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter) +{ + uint8_t max_tmds_clock; + ssize_t ret; + + /* native HDMI so no limit */ + if (type == DRM_DP_DUAL_MODE_NONE) + return 0; + + /* + * Type 1 adaptors are limited to 165MHz + * Type 2 adaptors can tells us their limit + */ + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) + return 165000; + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_MAX_TMDS_CLOCK, + &max_tmds_clock, sizeof(max_tmds_clock)); + if (ret || max_tmds_clock == 0x00 || max_tmds_clock == 0xff) { + DRM_DEBUG_KMS("Failed to query max TMDS clock\n"); + return 165000; + } + + return max_tmds_clock * 5000 / 2; +} +EXPORT_SYMBOL(drm_dp_dual_mode_max_tmds_clock); + +/** + * drm_dp_dual_mode_get_tmds_output - Get the state of the TMDS output buffers in the DP dual mode adaptor + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * @enabled: current state of the TMDS output buffers + * + * Get the state of the TMDS output buffers in the adaptor. For + * type2 adaptors this is queried from the DP_DUAL_MODE_TMDS_OEN + * register. As some type 1 adaptors have problems with registers + * (see comments in drm_dp_dual_mode_detect()) we don't read the + * register on those, instead we simply assume that the buffers + * are always enabled. + * + * Returns: + * 0 on success, negative error code on failure + */ +int drm_dp_dual_mode_get_tmds_output(enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter, + bool *enabled) +{ + uint8_t tmds_oen; + ssize_t ret; + + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) { + *enabled = true; + return 0; + } + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmds_oen, sizeof(tmds_oen)); + if (ret) { + DRM_DEBUG_KMS("Failed to query state of TMDS output buffers\n"); + return ret; + } + + *enabled = !(tmds_oen & DP_DUAL_MODE_TMDS_DISABLE); + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_get_tmds_output); + +/** + * drm_dp_dual_mode_set_tmds_output - Enable/disable TMDS output buffers in the DP dual mode adaptor + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * @enable: enable (as opposed to disable) the TMDS output buffers + * + * Set the state of the TMDS output buffers in the adaptor. For + * type2 this is set via the DP_DUAL_MODE_TMDS_OEN register. As + * some type 1 adaptors have problems with registers (see comments + * in drm_dp_dual_mode_detect()) we avoid touching the register, + * making this function a no-op on type 1 adaptors. + * + * Returns: + * 0 on success, negative error code on failure + */ +int drm_dp_dual_mode_set_tmds_output(enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter, bool enable) +{ + uint8_t tmds_oen = enable ? 0 : DP_DUAL_MODE_TMDS_DISABLE; + ssize_t ret; + + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) + return 0; + + ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmds_oen, sizeof(tmds_oen)); + if (ret) { + DRM_DEBUG_KMS("Failed to %s TMDS output buffers\n", + enable ? "enable" : "disable"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_set_tmds_output); + +/** + * drm_dp_get_dual_mode_type_name - Get the name of the DP dual mode adaptor type as a string + * @type: DP dual mode adaptor type + * + * Returns: + * String representation of the DP dual mode adaptor type + */ +const char *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type) +{ + switch (type) { + case DRM_DP_DUAL_MODE_NONE: + return "none"; + case DRM_DP_DUAL_MODE_TYPE1_DVI: + return "type 1 DVI"; + case DRM_DP_DUAL_MODE_TYPE1_HDMI: + return "type 1 HDMI"; + case DRM_DP_DUAL_MODE_TYPE2_DVI: + return "type 2 DVI"; + case DRM_DP_DUAL_MODE_TYPE2_HDMI: + return "type 2 HDMI"; + default: + WARN_ON(type != DRM_DP_DUAL_MODE_UNKNOWN); + return "unknown"; + } +} +EXPORT_SYMBOL(drm_dp_get_dual_mode_type_name); diff --git a/include/drm/drm_dp_dual_mode_helper.h b/include/drm/drm_dp_dual_mode_helper.h new file mode 100644 index 000000000000..e8a9dfd0e055 --- /dev/null +++ b/include/drm/drm_dp_dual_mode_helper.h @@ -0,0 +1,92 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DRM_DP_DUAL_MODE_HELPER_H +#define DRM_DP_DUAL_MODE_HELPER_H + +#include + +/* + * Optional for type 1 DVI adaptors + * Mandatory for type 1 HDMI and type 2 adaptors + */ +#define DP_DUAL_MODE_HDMI_ID 0x00 /* 00-0f */ +#define DP_DUAL_MODE_HDMI_ID_LEN 16 +/* + * Optional for type 1 adaptors + * Mandatory for type 2 adaptors + */ +#define DP_DUAL_MODE_ADAPTOR_ID 0x10 +#define DP_DUAL_MODE_REV_MASK 0x07 +#define DP_DUAL_MODE_REV_TYPE2 0x00 +#define DP_DUAL_MODE_TYPE_MASK 0xf0 +#define DP_DUAL_MODE_TYPE_TYPE2 0xa0 +#define DP_DUAL_MODE_IEEE_OUI 0x11 /* 11-13*/ +#define DP_DUAL_IEEE_OUI_LEN 3 +#define DP_DUAL_DEVICE_ID 0x14 /* 14-19 */ +#define DP_DUAL_DEVICE_ID_LEN 6 +#define DP_DUAL_MODE_HARDWARE_REV 0x1a +#define DP_DUAL_MODE_FIRMWARE_MAJOR_REV 0x1b +#define DP_DUAL_MODE_FIRMWARE_MINOR_REV 0x1c +#define DP_DUAL_MODE_MAX_TMDS_CLOCK 0x1d +#define DP_DUAL_MODE_I2C_SPEED_CAP 0x1e +#define DP_DUAL_MODE_TMDS_OEN 0x20 +#define DP_DUAL_MODE_TMDS_DISABLE 0x01 +#define DP_DUAL_MODE_HDMI_PIN_CTRL 0x21 +#define DP_DUAL_MODE_CEC_ENABLE 0x01 +#define DP_DUAL_MODE_I2C_SPEED_CTRL 0x22 + +struct i2c_adapter; + +ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter, + u8 offset, void *buffer, size_t size); +ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter, + u8 offset, const void *buffer, size_t size); + +/** + * enum drm_dp_dual_mode_type - Type of the DP dual mode adaptor + * @DRM_DP_DUAL_MODE_NONE: No DP dual mode adaptor + * @DRM_DP_DUAL_MODE_UNKNOWN: Could be either none or type 1 DVI adaptor + * @DRM_DP_DUAL_MODE_TYPE1_DVI: Type 1 DVI adaptor + * @DRM_DP_DUAL_MODE_TYPE1_HDMI: Type 1 HDMI adaptor + * @DRM_DP_DUAL_MODE_TYPE2_DVI: Type 2 DVI adaptor + * @DRM_DP_DUAL_MODE_TYPE2_HDMI: Type 2 HDMI adaptor + */ +enum drm_dp_dual_mode_type { + DRM_DP_DUAL_MODE_NONE, + DRM_DP_DUAL_MODE_UNKNOWN, + DRM_DP_DUAL_MODE_TYPE1_DVI, + DRM_DP_DUAL_MODE_TYPE1_HDMI, + DRM_DP_DUAL_MODE_TYPE2_DVI, + DRM_DP_DUAL_MODE_TYPE2_HDMI, +}; + +enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(struct i2c_adapter *adapter); +int drm_dp_dual_mode_max_tmds_clock(enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter); +int drm_dp_dual_mode_get_tmds_output(enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter, bool *enabled); +int drm_dp_dual_mode_set_tmds_output(enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter, bool enable); +const char *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type); + +#endif From c578d15226c99f0566d5d022f81af6b7d69928db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Mon, 2 May 2016 22:08:23 +0300 Subject: [PATCH 184/393] drm/i915: Respect DP++ adaptor TMDS clock limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Try to detect the max TMDS clock limit for the DP++ adaptor (if any) and take it into account when checking the port clock. Note that as with the sink (HDMI vs. DVI) TMDS clock limit we'll ignore the adaptor TMDS clock limit in the modeset path, in case users are already "overclocking" their TMDS links. One subtle change here is that we'll have to respect the adaptor TMDS clock limit when we decide whether to do 12bpc or 8bpc, otherwise we might end up picking 12bpc and accidentally driving the TMDS link out of spec even when the user chose a mode that fits wihting the limits at 8bpc. This means you can't "overclock" your DP++ dongle at 12bpc anymore, but you can continue to do so at 8bpc. Note that for simplicity we'll use the I2C access method for all dual mode adaptors including type 2. Otherwise we'd have to start mixing DP AUX and HDMI together. In the future we may need to do that if we come across any board designs that don't hook up the DDC pins to the DP++ connectors. Such boards would obviously only work with type 2 dual mode adaptors, and not type 1. v2: Store adaptor type under indel_hdmi->dp_dual_mode Deal with DRM_DP_DUAL_MODE_UNKNOWN Pass adaptor type to drm_dp_dual_mode_max_tmds_clock(), and use it for type1 adaptors as well Cc: stable@vger.kernel.org Reported-by: Tore Anderson Fixes: 7a0baa623446 ("Revert "drm/i915: Disable 12bpc hdmi for now"") Cc: Paulo Zanoni Cc: Shashank Sharma Cc: Daniel Vetter Signed-off-by: Ville Syrjälä Link: http://patchwork.freedesktop.org/patch/msgid/1462216105-20881-3-git-send-email-ville.syrjala@linux.intel.com Reviewed-by: Shashank Sharma (cherry picked from commit b1ba124d8e95cca48d33502a4a76b1ed09d213ce) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_drv.h | 5 +++ drivers/gpu/drm/i915/intel_hdmi.c | 58 ++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index b9f1304439e2..5699bb67fd2a 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -753,6 +754,10 @@ struct cxsr_latency { struct intel_hdmi { i915_reg_t hdmi_reg; int ddc_bus; + struct { + enum drm_dp_dual_mode_type type; + int max_tmds_clock; + } dp_dual_mode; bool limited_color_range; bool color_range_auto; bool has_hdmi_sink; diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 2cdab73046f8..dfd895d16d6b 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -1165,27 +1165,42 @@ static void pch_post_disable_hdmi(struct intel_encoder *encoder) intel_disable_hdmi(encoder); } -static int hdmi_port_clock_limit(struct intel_hdmi *hdmi, bool respect_dvi_limit) +static int intel_hdmi_source_max_tmds_clock(struct drm_i915_private *dev_priv) { - struct drm_device *dev = intel_hdmi_to_dev(hdmi); - - if ((respect_dvi_limit && !hdmi->has_hdmi_sink) || IS_G4X(dev)) + if (IS_G4X(dev_priv)) return 165000; - else if (IS_HASWELL(dev) || INTEL_INFO(dev)->gen >= 8) + else if (IS_HASWELL(dev_priv) || INTEL_INFO(dev_priv)->gen >= 8) return 300000; else return 225000; } +static int hdmi_port_clock_limit(struct intel_hdmi *hdmi, + bool respect_downstream_limits) +{ + struct drm_device *dev = intel_hdmi_to_dev(hdmi); + int max_tmds_clock = intel_hdmi_source_max_tmds_clock(to_i915(dev)); + + if (respect_downstream_limits) { + if (hdmi->dp_dual_mode.max_tmds_clock) + max_tmds_clock = min(max_tmds_clock, + hdmi->dp_dual_mode.max_tmds_clock); + if (!hdmi->has_hdmi_sink) + max_tmds_clock = min(max_tmds_clock, 165000); + } + + return max_tmds_clock; +} + static enum drm_mode_status hdmi_port_clock_valid(struct intel_hdmi *hdmi, - int clock, bool respect_dvi_limit) + int clock, bool respect_downstream_limits) { struct drm_device *dev = intel_hdmi_to_dev(hdmi); if (clock < 25000) return MODE_CLOCK_LOW; - if (clock > hdmi_port_clock_limit(hdmi, respect_dvi_limit)) + if (clock > hdmi_port_clock_limit(hdmi, respect_downstream_limits)) return MODE_CLOCK_HIGH; /* BXT DPLL can't generate 223-240 MHz */ @@ -1309,7 +1324,7 @@ bool intel_hdmi_compute_config(struct intel_encoder *encoder, * within limits. */ if (pipe_config->pipe_bpp > 8*3 && pipe_config->has_hdmi_sink && - hdmi_port_clock_valid(intel_hdmi, clock_12bpc, false) == MODE_OK && + hdmi_port_clock_valid(intel_hdmi, clock_12bpc, true) == MODE_OK && hdmi_12bpc_possible(pipe_config)) { DRM_DEBUG_KMS("picking bpc to 12 for HDMI output\n"); desired_bpp = 12*3; @@ -1349,10 +1364,35 @@ intel_hdmi_unset_edid(struct drm_connector *connector) intel_hdmi->has_audio = false; intel_hdmi->rgb_quant_range_selectable = false; + intel_hdmi->dp_dual_mode.type = DRM_DP_DUAL_MODE_NONE; + intel_hdmi->dp_dual_mode.max_tmds_clock = 0; + kfree(to_intel_connector(connector)->detect_edid); to_intel_connector(connector)->detect_edid = NULL; } +static void +intel_hdmi_dp_dual_mode_detect(struct drm_connector *connector) +{ + struct drm_i915_private *dev_priv = to_i915(connector->dev); + struct intel_hdmi *hdmi = intel_attached_hdmi(connector); + struct i2c_adapter *adapter = + intel_gmbus_get_adapter(dev_priv, hdmi->ddc_bus); + enum drm_dp_dual_mode_type type = drm_dp_dual_mode_detect(adapter); + + if (type == DRM_DP_DUAL_MODE_NONE || + type == DRM_DP_DUAL_MODE_UNKNOWN) + return; + + hdmi->dp_dual_mode.type = type; + hdmi->dp_dual_mode.max_tmds_clock = + drm_dp_dual_mode_max_tmds_clock(type, adapter); + + DRM_DEBUG_KMS("DP dual mode adaptor (%s) detected (max TMDS clock: %d kHz)\n", + drm_dp_get_dual_mode_type_name(type), + hdmi->dp_dual_mode.max_tmds_clock); +} + static bool intel_hdmi_set_edid(struct drm_connector *connector, bool force) { @@ -1368,6 +1408,8 @@ intel_hdmi_set_edid(struct drm_connector *connector, bool force) intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus)); + intel_hdmi_dp_dual_mode_detect(connector); + intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS); } From 0c2fb7c6c8fef2092051192095a7788231ba9a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Mon, 2 May 2016 22:08:24 +0300 Subject: [PATCH 185/393] drm/i915: Enable/disable TMDS output buffers in DP++ adaptor as needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To save a bit of power, let's try to turn off the TMDS output buffers in DP++ adaptors when we're not driving the port. v2: Let's not forget DDI, toss in a debug message while at it v3: Just do the TMDS output control based on adaptor type. With the helper getting passed the type, we wouldn't actually have to check at all in the driver, but the check eliminates the debug output more honest Cc: stable@vger.kernel.org Cc: Tore Anderson Cc: Paulo Zanoni Cc: Shashank Sharma Cc: Daniel Vetter Signed-off-by: Ville Syrjälä Link: http://patchwork.freedesktop.org/patch/msgid/1462216105-20881-4-git-send-email-ville.syrjala@linux.intel.com Reviewed-by: Shashank Sharma (cherry picked from commit b2ccb822d376d1bbbe5d1f9118d1488b25e6bc6d) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_ddi.c | 12 ++++++++++++ drivers/gpu/drm/i915/intel_drv.h | 1 + drivers/gpu/drm/i915/intel_hdmi.c | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index e30e1781fd71..eb59ca19e5b9 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -1601,6 +1601,12 @@ static void intel_ddi_pre_enable(struct intel_encoder *intel_encoder) enum port port = intel_ddi_get_encoder_port(intel_encoder); int type = intel_encoder->type; + if (type == INTEL_OUTPUT_HDMI) { + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + + intel_dp_dual_mode_set_tmds_output(intel_hdmi, true); + } + intel_prepare_ddi_buffer(intel_encoder); if (type == INTEL_OUTPUT_EDP) { @@ -1667,6 +1673,12 @@ static void intel_ddi_post_disable(struct intel_encoder *intel_encoder) DPLL_CTRL2_DDI_CLK_OFF(port))); else if (INTEL_INFO(dev)->gen < 9) I915_WRITE(PORT_CLK_SEL(port), PORT_CLK_SEL_NONE); + + if (type == INTEL_OUTPUT_HDMI) { + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + + intel_dp_dual_mode_set_tmds_output(intel_hdmi, false); + } } static void intel_enable_ddi(struct intel_encoder *intel_encoder) diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 5699bb67fd2a..0d06113fbfb1 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -1408,6 +1408,7 @@ void intel_hdmi_init_connector(struct intel_digital_port *intel_dig_port, struct intel_hdmi *enc_to_intel_hdmi(struct drm_encoder *encoder); bool intel_hdmi_compute_config(struct intel_encoder *encoder, struct intel_crtc_state *pipe_config); +void intel_dp_dual_mode_set_tmds_output(struct intel_hdmi *hdmi, bool enable); /* intel_lvds.c */ diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index dfd895d16d6b..75cbaed36875 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -836,6 +836,22 @@ static void hsw_set_infoframes(struct drm_encoder *encoder, intel_hdmi_set_hdmi_infoframe(encoder, adjusted_mode); } +void intel_dp_dual_mode_set_tmds_output(struct intel_hdmi *hdmi, bool enable) +{ + struct drm_i915_private *dev_priv = to_i915(intel_hdmi_to_dev(hdmi)); + struct i2c_adapter *adapter = + intel_gmbus_get_adapter(dev_priv, hdmi->ddc_bus); + + if (hdmi->dp_dual_mode.type < DRM_DP_DUAL_MODE_TYPE2_DVI) + return; + + DRM_DEBUG_KMS("%s DP dual mode adaptor TMDS output\n", + enable ? "Enabling" : "Disabling"); + + drm_dp_dual_mode_set_tmds_output(hdmi->dp_dual_mode.type, + adapter, enable); +} + static void intel_hdmi_prepare(struct intel_encoder *encoder) { struct drm_device *dev = encoder->base.dev; @@ -845,6 +861,8 @@ static void intel_hdmi_prepare(struct intel_encoder *encoder) const struct drm_display_mode *adjusted_mode = &crtc->config->base.adjusted_mode; u32 hdmi_val; + intel_dp_dual_mode_set_tmds_output(intel_hdmi, true); + hdmi_val = SDVO_ENCODING_HDMI; if (!HAS_PCH_SPLIT(dev) && crtc->config->limited_color_range) hdmi_val |= HDMI_COLOR_RANGE_16_235; @@ -1140,6 +1158,8 @@ static void intel_disable_hdmi(struct intel_encoder *encoder) } intel_hdmi->set_infoframes(&encoder->base, false, NULL); + + intel_dp_dual_mode_set_tmds_output(intel_hdmi, false); } static void g4x_disable_hdmi(struct intel_encoder *encoder) From 55d7f30ee1096474cb5bdbca46b60f23ce8b1f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 4 May 2016 14:45:22 +0300 Subject: [PATCH 186/393] drm/i915: Determine DP++ type 1 DVI adaptor presence based on VBT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DP dual mode type 1 DVI adaptors aren't required to implement any registers, so it's a bit hard to detect them. The best way would be to check the state of the CONFIG1 pin, but we have no way to do that. So as a last resort, check the VBT to see if the HDMI port is in fact a dual mode capable DP port. v2: Deal with VBT code reorganization Deal with DRM_DP_DUAL_MODE_UNKNOWN Reduce DEVICE_TYPE_DP_DUAL_MODE_BITS a bit Accept both DP and HDMI dvo_port in VBT as my BSW at least declare its DP port as HDMI :( v3: Ignore DEVICE_TYPE_NOT_HDMI_OUTPUT (Shashank) Cc: stable@vger.kernel.org Cc: Tore Anderson Reported-by: Tore Anderson Fixes: 7a0baa623446 ("Revert "drm/i915: Disable 12bpc hdmi for now"") Cc: Paulo Zanoni Cc: Shashank Sharma Cc: Daniel Vetter Signed-off-by: Ville Syrjälä Link: http://patchwork.freedesktop.org/patch/msgid/1462362322-31278-1-git-send-email-ville.syrjala@linux.intel.com Reviewed-by: Shashank Sharma (cherry picked from commit d61992565bd3cc5b66d74ed2e96df043c2a207e2) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_drv.h | 1 + drivers/gpu/drm/i915/intel_bios.c | 36 +++++++++++++++++++++++++++ drivers/gpu/drm/i915/intel_hdmi.c | 30 +++++++++++++++++++--- drivers/gpu/drm/i915/intel_vbt_defs.h | 12 +++++++++ 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 9d7b54ea14f9..250992995ac8 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -3482,6 +3482,7 @@ bool intel_bios_is_valid_vbt(const void *buf, size_t size); bool intel_bios_is_tv_present(struct drm_i915_private *dev_priv); bool intel_bios_is_lvds_present(struct drm_i915_private *dev_priv, u8 *i2c_pin); bool intel_bios_is_port_edp(struct drm_i915_private *dev_priv, enum port port); +bool intel_bios_is_port_dp_dual_mode(struct drm_i915_private *dev_priv, enum port port); bool intel_bios_is_dsi_present(struct drm_i915_private *dev_priv, enum port *port); bool intel_bios_is_port_hpd_inverted(struct drm_i915_private *dev_priv, enum port port); diff --git a/drivers/gpu/drm/i915/intel_bios.c b/drivers/gpu/drm/i915/intel_bios.c index e72dd9a8d6bf..b235b6e88ead 100644 --- a/drivers/gpu/drm/i915/intel_bios.c +++ b/drivers/gpu/drm/i915/intel_bios.c @@ -1578,6 +1578,42 @@ bool intel_bios_is_port_edp(struct drm_i915_private *dev_priv, enum port port) return false; } +bool intel_bios_is_port_dp_dual_mode(struct drm_i915_private *dev_priv, enum port port) +{ + static const struct { + u16 dp, hdmi; + } port_mapping[] = { + /* + * Buggy VBTs may declare DP ports as having + * HDMI type dvo_port :( So let's check both. + */ + [PORT_B] = { DVO_PORT_DPB, DVO_PORT_HDMIB, }, + [PORT_C] = { DVO_PORT_DPC, DVO_PORT_HDMIC, }, + [PORT_D] = { DVO_PORT_DPD, DVO_PORT_HDMID, }, + [PORT_E] = { DVO_PORT_DPE, DVO_PORT_HDMIE, }, + }; + int i; + + if (port == PORT_A || port >= ARRAY_SIZE(port_mapping)) + return false; + + if (!dev_priv->vbt.child_dev_num) + return false; + + for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { + const union child_device_config *p_child = + &dev_priv->vbt.child_dev[i]; + + if ((p_child->common.dvo_port == port_mapping[port].dp || + p_child->common.dvo_port == port_mapping[port].hdmi) && + (p_child->common.device_type & DEVICE_TYPE_DP_DUAL_MODE_BITS) == + (DEVICE_TYPE_DP_DUAL_MODE & DEVICE_TYPE_DP_DUAL_MODE_BITS)) + return true; + } + + return false; +} + /** * intel_bios_is_dsi_present - is DSI present in VBT * @dev_priv: i915 device instance diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 75cbaed36875..af3ded169e3a 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -1392,16 +1392,38 @@ intel_hdmi_unset_edid(struct drm_connector *connector) } static void -intel_hdmi_dp_dual_mode_detect(struct drm_connector *connector) +intel_hdmi_dp_dual_mode_detect(struct drm_connector *connector, bool has_edid) { struct drm_i915_private *dev_priv = to_i915(connector->dev); struct intel_hdmi *hdmi = intel_attached_hdmi(connector); + enum port port = hdmi_to_dig_port(hdmi)->port; struct i2c_adapter *adapter = intel_gmbus_get_adapter(dev_priv, hdmi->ddc_bus); enum drm_dp_dual_mode_type type = drm_dp_dual_mode_detect(adapter); - if (type == DRM_DP_DUAL_MODE_NONE || - type == DRM_DP_DUAL_MODE_UNKNOWN) + /* + * Type 1 DVI adaptors are not required to implement any + * registers, so we can't always detect their presence. + * Ideally we should be able to check the state of the + * CONFIG1 pin, but no such luck on our hardware. + * + * The only method left to us is to check the VBT to see + * if the port is a dual mode capable DP port. But let's + * only do that when we sucesfully read the EDID, to avoid + * confusing log messages about DP dual mode adaptors when + * there's nothing connected to the port. + */ + if (type == DRM_DP_DUAL_MODE_UNKNOWN) { + if (has_edid && + intel_bios_is_port_dp_dual_mode(dev_priv, port)) { + DRM_DEBUG_KMS("Assuming DP dual mode adaptor presence based on VBT\n"); + type = DRM_DP_DUAL_MODE_TYPE1_DVI; + } else { + type = DRM_DP_DUAL_MODE_NONE; + } + } + + if (type == DRM_DP_DUAL_MODE_NONE) return; hdmi->dp_dual_mode.type = type; @@ -1428,7 +1450,7 @@ intel_hdmi_set_edid(struct drm_connector *connector, bool force) intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus)); - intel_hdmi_dp_dual_mode_detect(connector); + intel_hdmi_dp_dual_mode_detect(connector, edid != NULL); intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS); } diff --git a/drivers/gpu/drm/i915/intel_vbt_defs.h b/drivers/gpu/drm/i915/intel_vbt_defs.h index 9ff1e960d617..c15051de8023 100644 --- a/drivers/gpu/drm/i915/intel_vbt_defs.h +++ b/drivers/gpu/drm/i915/intel_vbt_defs.h @@ -740,6 +740,7 @@ struct bdb_psr { #define DEVICE_TYPE_INT_TV 0x1009 #define DEVICE_TYPE_HDMI 0x60D2 #define DEVICE_TYPE_DP 0x68C6 +#define DEVICE_TYPE_DP_DUAL_MODE 0x60D6 #define DEVICE_TYPE_eDP 0x78C6 #define DEVICE_TYPE_CLASS_EXTENSION (1 << 15) @@ -774,6 +775,17 @@ struct bdb_psr { DEVICE_TYPE_DISPLAYPORT_OUTPUT | \ DEVICE_TYPE_ANALOG_OUTPUT) +#define DEVICE_TYPE_DP_DUAL_MODE_BITS \ + (DEVICE_TYPE_INTERNAL_CONNECTOR | \ + DEVICE_TYPE_MIPI_OUTPUT | \ + DEVICE_TYPE_COMPOSITE_OUTPUT | \ + DEVICE_TYPE_LVDS_SINGALING | \ + DEVICE_TYPE_TMDS_DVI_SIGNALING | \ + DEVICE_TYPE_VIDEO_SIGNALING | \ + DEVICE_TYPE_DISPLAYPORT_OUTPUT | \ + DEVICE_TYPE_DIGITAL_OUTPUT | \ + DEVICE_TYPE_ANALOG_OUTPUT) + /* define the DVO port for HDMI output type */ #define DVO_B 1 #define DVO_C 2 From 5fbd0418eef283269f2cc1b90b63d88f20c887bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Fri, 6 May 2016 21:35:55 +0300 Subject: [PATCH 187/393] drm/i915: Re-enable GGTT earlier during resume on pre-gen6 platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the intel_enable_gtt() call to happen before we touch the GTT during resume. Right now it's done way too late. Before commit ebb7c78d358b ("agp/intel-gtt: Only register fake agp driver for gen1") it was actually done earlier on account of also getting called from the resume hook of the fake agp driver. With the fake agp driver no longer getting registered we must move the call up. The symptoms I've seen on my 830 machine include lowmem corruption, other kinds of memory corruption, and straight up hung machine during or just after resume. Not really sure what causes the memory corruption, but so far I've not seen any with this fix. I think we shouldn't really need to call this during init, but we have been doing that so I've decided to keep the call. However moving that call earlier could be prudent as well. Doing it right after the intel-gtt probe seems appropriate. Also tested this on 946gz,elk,ilk and all seemed quite happy with this change. v2: Reorder init_hw vs. enable_hw functions (Chris) Cc: Daniel Vetter Cc: drm-intel-fixes@lists.freedesktop.org Fixes: ebb7c78d358b ("agp/intel-gtt: Only register fake agp driver for gen1") Signed-off-by: Ville Syrjälä Link: http://patchwork.freedesktop.org/patch/msgid/1462559755-353-1-git-send-email-ville.syrjala@linux.intel.com Reviewed-by: Chris Wilson (cherry picked from commit ac840ae53573d9f435c88c131f6707a79aecb466) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_dma.c | 6 ++++++ drivers/gpu/drm/i915/i915_drv.c | 5 +++++ drivers/gpu/drm/i915/i915_gem.c | 3 --- drivers/gpu/drm/i915/i915_gem_gtt.c | 8 ++++++++ drivers/gpu/drm/i915/i915_gem_gtt.h | 1 + 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index 5c7615041b31..157af1433889 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -1186,6 +1186,12 @@ static int i915_driver_init_hw(struct drm_i915_private *dev_priv) if (ret) return ret; + ret = i915_ggtt_enable_hw(dev); + if (ret) { + DRM_ERROR("failed to enable GGTT\n"); + goto out_ggtt; + } + /* WARNING: Apparently we must kick fbdev drivers before vgacon, * otherwise the vga fbdev driver falls over. */ ret = i915_kick_out_firmware_fb(dev_priv); diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 7a0e4d6c71e2..dd36760f7da6 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -734,9 +734,14 @@ int i915_suspend_switcheroo(struct drm_device *dev, pm_message_t state) static int i915_drm_resume(struct drm_device *dev) { struct drm_i915_private *dev_priv = dev->dev_private; + int ret; disable_rpm_wakeref_asserts(dev_priv); + ret = i915_ggtt_enable_hw(dev); + if (ret) + DRM_ERROR("failed to re-enable GGTT\n"); + intel_csr_ucode_resume(dev_priv); mutex_lock(&dev->struct_mutex); diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index a14a98341a4b..640c6c1ee72e 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -4860,9 +4860,6 @@ i915_gem_init_hw(struct drm_device *dev) struct intel_engine_cs *engine; int ret, j; - if (INTEL_INFO(dev)->gen < 6 && !intel_enable_gtt()) - return -EIO; - /* Double layer security blanket, see i915_gem_init() */ intel_uncore_forcewake_get(dev_priv, FORCEWAKE_ALL); diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.c b/drivers/gpu/drm/i915/i915_gem_gtt.c index 0d666b3f7e9b..92acdff9dad3 100644 --- a/drivers/gpu/drm/i915/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/i915_gem_gtt.c @@ -3236,6 +3236,14 @@ int i915_ggtt_init_hw(struct drm_device *dev) return ret; } +int i915_ggtt_enable_hw(struct drm_device *dev) +{ + if (INTEL_INFO(dev)->gen < 6 && !intel_enable_gtt()) + return -EIO; + + return 0; +} + void i915_gem_restore_gtt_mappings(struct drm_device *dev) { struct drm_i915_private *dev_priv = to_i915(dev); diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.h b/drivers/gpu/drm/i915/i915_gem_gtt.h index d7dd3d8a8758..0008543d55f6 100644 --- a/drivers/gpu/drm/i915/i915_gem_gtt.h +++ b/drivers/gpu/drm/i915/i915_gem_gtt.h @@ -514,6 +514,7 @@ i915_page_dir_dma_addr(const struct i915_hw_ppgtt *ppgtt, const unsigned n) } int i915_ggtt_init_hw(struct drm_device *dev); +int i915_ggtt_enable_hw(struct drm_device *dev); void i915_gem_init_ggtt(struct drm_device *dev); void i915_ggtt_cleanup_hw(struct drm_device *dev); From e32da7adaf3c511e7e6ffa0d5b680e07201630b8 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Wed, 27 Apr 2016 09:02:01 +0100 Subject: [PATCH 188/393] drm/i915: Protect gen7 irq_seqno_barrier with uncore lock Faced with sporadic machine hangs on gen7, that mimic the issue of concurrent writes to the same cacheline and seem to start with commit 9b9ed3093613 (drm/i915: Remove forcewake dance from seqno/irq barrier on legacy gen6+), let us restore the spinlock around the mmio read. Fixes: 9b9ed3093613 (drm/i915: Remove forcewake dance from seqno/irq...) Signed-off-by: Chris Wilson Cc: Mika Kuoppala Link: http://patchwork.freedesktop.org/patch/msgid/1461744121-27051-1-git-send-email-chris@chris-wilson.co.uk Tested-by: Mika Kuoppala Reviewed-by: Mika Kuoppala (cherry picked from commit bcbdb6d01150dcc1769ddc9baaaf7f102f27f919) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_ringbuffer.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/intel_ringbuffer.c b/drivers/gpu/drm/i915/intel_ringbuffer.c index 245386e20c52..fd93987b16f2 100644 --- a/drivers/gpu/drm/i915/intel_ringbuffer.c +++ b/drivers/gpu/drm/i915/intel_ringbuffer.c @@ -1573,6 +1573,8 @@ pc_render_add_request(struct drm_i915_gem_request *req) static void gen6_seqno_barrier(struct intel_engine_cs *engine) { + struct drm_i915_private *dev_priv = engine->dev->dev_private; + /* Workaround to force correct ordering between irq and seqno writes on * ivb (and maybe also on snb) by reading from a CS register (like * ACTHD) before reading the status page. @@ -1584,9 +1586,13 @@ gen6_seqno_barrier(struct intel_engine_cs *engine) * the write time to land, but that would incur a delay after every * batch i.e. much more frequent than a delay when waiting for the * interrupt (with the same net latency). + * + * Also note that to prevent whole machine hangs on gen7, we have to + * take the spinlock to guard against concurrent cacheline access. */ - struct drm_i915_private *dev_priv = engine->dev->dev_private; + spin_lock_irq(&dev_priv->uncore.lock); POSTING_READ_FW(RING_ACTHD(engine->mmio_base)); + spin_unlock_irq(&dev_priv->uncore.lock); } static u32 From 130b62f74af3600897714045fcab553f01f46df4 Mon Sep 17 00:00:00 2001 From: Ramalingam C Date: Tue, 19 Apr 2016 13:48:13 +0530 Subject: [PATCH 189/393] drm/i915/BXT: Retrieving the horizontal timing for DSI Retriving the horizontal timings from the port registers as part of get_config(). This fixes a division by zero: [ 56.916557] divide error: 0000 [#1] PREEMPT SMP [ 56.921741] Modules linked in: i915(+) drm_kms_helper syscopyarea sysfillrect sysimgblt fb_sys_fops drm intel_gtt agpgart cf g80211 rfkill binfmt_misc ax88179_178a kvm_intel kvm irqbypass crc32c_intel efivars tpm_tis tpm fuse [ 56.944106] CPU: 3 PID: 1097 Comm: modprobe Not tainted 4.6.0-rc4+ #433 [ 56.951501] Hardware name: Intel Corp. Broxton M/RVP, BIOS BXT1RVPA.X64.0131.B30.1604142217 04/14/2016 [ 56.961908] task: ffff88007a854d00 ti: ffff88007aea0000 task.ti: ffff88007aea0000 [ 56.970273] RIP: 0010:[] [] drm_mode_hsync+0x22/0x40 [drm] [ 56.980043] RSP: 0018:ffff88007aea3788 EFLAGS: 00010206 [ 56.985982] RAX: 000000000788b600 RBX: ffff880073c22108 RCX: 0000000000000000 [ 56.993957] RDX: 0000000000000000 RSI: ffff88007ab06800 RDI: ffff880073c22108 [ 57.001935] RBP: ffff88007aea3788 R08: 0000000000000001 R09: ffff880073c221e8 [ 57.009903] R10: ffff880073c22108 R11: 0000000000000001 R12: ffff88007a300000 [ 57.017872] R13: ffff880073c22000 R14: ffff880175f78000 R15: ffff880175f78798 [ 57.025849] FS: 00007f105d3e6700(0000) GS:ffff88017fd80000(0000) knlGS:0000000000000000 [ 57.034894] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 57.041317] CR2: 00007f4d485101d0 CR3: 000000007a820000 CR4: 00000000003406e0 [ 57.049292] Stack: [ 57.051539] ffff88007aea37a0 ffffffffa043b632 ffff880175f787c8 ffff88007aea3810 [ 57.059825] ffffffffa043d59e ffff880175f787b0 ffff88007ab68c00 ffff88007aea37f0 [ 57.068128] ffff880073c221e8 ffff880073c22108 ffff880175f78780 ffff880100000000 [ 57.076430] Call Trace: [ 57.079254] [] intel_mode_from_pipe_config+0x82/0xb0 [i915] [ 57.087405] [] intel_modeset_setup_hw_state+0x55e/0xd60 [i915] [ 57.095847] [] intel_modeset_init+0x8e4/0x1630 [i915] [ 57.103415] [] i915_driver_load+0xbe0/0x1980 [i915] [ 57.110745] [] drm_dev_register+0xa9/0xc0 [drm] [ 57.117681] [] drm_get_pci_dev+0x8d/0x1e0 [drm] [ 57.124600] [] ? _raw_spin_unlock_irqrestore+0x42/0x70 [ 57.132253] [] i915_pci_probe+0x34/0x50 [i915] [ 57.139070] [] local_pci_probe+0x45/0xa0 [ 57.145303] [] ? pci_match_device+0xe0/0x110 [ 57.151924] [] pci_device_probe+0xdb/0x130 [ 57.158355] [] driver_probe_device+0x223/0x440 [ 57.165169] [] __driver_attach+0xd5/0x100 [ 57.171500] [] ? driver_probe_device+0x440/0x440 [ 57.178510] [] bus_for_each_dev+0x66/0xa0 [ 57.184841] [] driver_attach+0x1e/0x20 [ 57.190881] [] bus_add_driver+0x1ee/0x280 [ 57.197212] [] driver_register+0x60/0xe0 [ 57.203447] [] __pci_register_driver+0x60/0x70 [ 57.210285] [] drm_pci_init+0xe0/0x110 [drm] [ 57.216911] [] ? trace_hardirqs_on+0xd/0x10 [ 57.223434] [] ? 0xffffffffa023a000 [ 57.229237] [] i915_init+0x92/0x99 [i915] [ 57.235570] [] do_one_initcall+0xab/0x1d0 [ 57.241900] [] ? rcu_read_lock_sched_held+0x7f/0x90 [ 57.249205] [] ? kmem_cache_alloc_trace+0x248/0x2b0 [ 57.256509] [] ? do_init_module+0x27/0x1d9 [ 57.262934] [] do_init_module+0x5f/0x1d9 [ 57.269167] [] load_module+0x20ef/0x27b0 [ 57.275401] [] ? store_uevent+0x40/0x40 [ 57.281541] [] SYSC_finit_module+0xc3/0xf0 [ 57.287969] [] SyS_finit_module+0xe/0x10 [ 57.294203] [] entry_SYSCALL_64_fastpath+0x1c/0xac [ 57.301406] Code: ff 5d c3 66 0f 1f 44 00 00 0f 1f 44 00 00 8b 87 d8 00 00 00 55 48 89 e5 85 c0 75 22 8b 4f 68 85 c9 78 1b 69 47 58 e8 03 00 00 99 f9 b9 d3 4d 62 10 05 f4 01 00 00 f7 e1 89 d0 c1 e8 06 5d c3 [ 57.322964] RIP [] drm_mode_hsync+0x22/0x40 [drm] [ 57.330103] RSP [ 57.334276] ---[ end trace d414224cb2e2a4cf ]--- [ 57.339861] modprobe (1097) used greatest stack depth: 12048 bytes left Fixes: 6f0e7535e7e1 ("drm/i915/BXT: Get pipe conf from the port registers") Signed-off-by: Ramalingam C Acked-by: Imre Deak Signed-off-by: Jani Nikula Link: http://patchwork.freedesktop.org/patch/msgid/1461053894-5058-1-git-send-email-ramalingam.c@intel.com (cherry picked from commit cefc4e18785123326c5d4d985085e32160fef7f5) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_dsi.c | 44 +++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_dsi.c b/drivers/gpu/drm/i915/intel_dsi.c index 2b22bb9bb86f..efa306bb19c9 100644 --- a/drivers/gpu/drm/i915/intel_dsi.c +++ b/drivers/gpu/drm/i915/intel_dsi.c @@ -46,6 +46,14 @@ static const struct { }, }; +/* return pixels equvalent to txbyteclkhs */ +static u16 pixels_from_txbyteclkhs(u16 clk_hs, int bpp, int lane_count, + u16 burst_mode_ratio) +{ + return DIV_ROUND_UP((clk_hs * lane_count * 8 * 100), + (bpp * burst_mode_ratio)); +} + enum mipi_dsi_pixel_format pixel_format_from_register_bits(u32 fmt) { /* It just so happens the VBT matches register contents. */ @@ -781,9 +789,10 @@ static void bxt_dsi_get_pipe_config(struct intel_encoder *encoder, struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); + unsigned int lane_count = intel_dsi->lane_count; unsigned int bpp, fmt; enum port port; - u16 vfp, vsync, vbp; + u16 hactive, hfp, hsync, hbp, vfp, vsync, vbp; /* * Atleast one port is active as encoder->get_config called only if @@ -808,22 +817,43 @@ static void bxt_dsi_get_pipe_config(struct intel_encoder *encoder, adjusted_mode->crtc_vtotal = I915_READ(BXT_MIPI_TRANS_VTOTAL(port)); + hactive = adjusted_mode->crtc_hdisplay; + hfp = I915_READ(MIPI_HFP_COUNT(port)); + /* - * TODO: Retrieve hfp, hsync and hbp. Adjust them for dual link and - * calculate hsync_start, hsync_end, htotal and hblank_end + * Meaningful for video mode non-burst sync pulse mode only, + * can be zero for non-burst sync events and burst modes */ + hsync = I915_READ(MIPI_HSYNC_PADDING_COUNT(port)); + hbp = I915_READ(MIPI_HBP_COUNT(port)); + + /* harizontal values are in terms of high speed byte clock */ + hfp = pixels_from_txbyteclkhs(hfp, bpp, lane_count, + intel_dsi->burst_mode_ratio); + hsync = pixels_from_txbyteclkhs(hsync, bpp, lane_count, + intel_dsi->burst_mode_ratio); + hbp = pixels_from_txbyteclkhs(hbp, bpp, lane_count, + intel_dsi->burst_mode_ratio); + + if (intel_dsi->dual_link) { + hfp *= 2; + hsync *= 2; + hbp *= 2; + } /* vertical values are in terms of lines */ vfp = I915_READ(MIPI_VFP_COUNT(port)); vsync = I915_READ(MIPI_VSYNC_PADDING_COUNT(port)); vbp = I915_READ(MIPI_VBP_COUNT(port)); + adjusted_mode->crtc_htotal = hactive + hfp + hsync + hbp; + adjusted_mode->crtc_hsync_start = hfp + adjusted_mode->crtc_hdisplay; + adjusted_mode->crtc_hsync_end = hsync + adjusted_mode->crtc_hsync_start; adjusted_mode->crtc_hblank_start = adjusted_mode->crtc_hdisplay; + adjusted_mode->crtc_hblank_end = adjusted_mode->crtc_htotal; - adjusted_mode->crtc_vsync_start = - vfp + adjusted_mode->crtc_vdisplay; - adjusted_mode->crtc_vsync_end = - vsync + adjusted_mode->crtc_vsync_start; + adjusted_mode->crtc_vsync_start = vfp + adjusted_mode->crtc_vdisplay; + adjusted_mode->crtc_vsync_end = vsync + adjusted_mode->crtc_vsync_start; adjusted_mode->crtc_vblank_start = adjusted_mode->crtc_vdisplay; adjusted_mode->crtc_vblank_end = adjusted_mode->crtc_vtotal; } From 2bae0304684cab917f348ec411cae600130fa6a9 Mon Sep 17 00:00:00 2001 From: Ander Conselvan de Oliveira Date: Wed, 27 Apr 2016 15:44:16 +0300 Subject: [PATCH 190/393] drm/i915: Set crtc_state->lane_count for HDMI Set the lane count for HDMI to 4. This will make it easier to unduplicate CHV phy code. This also fixes the the soft reset programming for HDMI with CHV. After commit a8f327fb8464 ("drm/i915: Clean up CHV lane soft reset programming"), it wouldn't set the right bits for PCS23 since it relied on a lane count that was never set. v2: Set lane_count in *_get_config() to please state checker. (0day) v3: Set lane_count for DDI in DVI mode too. (CI) v4: Add note about CHV soft lane reset. (Ander) Fixes: a8f327fb8464 ("drm/i915: Clean up CHV lane soft reset programming") Signed-off-by: Ander Conselvan de Oliveira Reviewed-by: Jim Bride Link: http://patchwork.freedesktop.org/patch/msgid/1461761065-21195-2-git-send-email-ander.conselvan.de.oliveira@intel.com (cherry picked from commit d4d6279abe9a4a2d52115bad122118db4995df17) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_ddi.c | 4 +++- drivers/gpu/drm/i915/intel_hdmi.c | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index eb59ca19e5b9..a3600704e6d4 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -2209,8 +2209,10 @@ void intel_ddi_get_config(struct intel_encoder *encoder, if (intel_hdmi->infoframe_enabled(&encoder->base, pipe_config)) pipe_config->has_infoframe = true; - break; + /* fall through */ case TRANS_DDI_MODE_SELECT_DVI: + pipe_config->lane_count = 4; + break; case TRANS_DDI_MODE_SELECT_FDI: break; case TRANS_DDI_MODE_SELECT_DP_SST: diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index af3ded169e3a..2c3bd9c2573e 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -971,6 +971,8 @@ static void intel_hdmi_get_config(struct intel_encoder *encoder, dotclock /= pipe_config->pixel_multiplier; pipe_config->base.adjusted_mode.crtc_clock = dotclock; + + pipe_config->lane_count = 4; } static void intel_enable_hdmi_audio(struct intel_encoder *encoder) @@ -1372,6 +1374,8 @@ bool intel_hdmi_compute_config(struct intel_encoder *encoder, /* Set user selected PAR to incoming mode's member */ adjusted_mode->picture_aspect_ratio = intel_hdmi->aspect_ratio; + pipe_config->lane_count = 4; + return true; } From 83e538020018e3235f5ec40a1f2dfc673bb5ae3c Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Fri, 29 Apr 2016 13:18:23 +0100 Subject: [PATCH 191/393] drm/i915: Fix gen8 semaphores id for legacy mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the introduction of a distinct engine->id vs the hardware id, we need to fix up the value we use for selecting the target engine when signaling a semaphore. Note that these values can be merged with engine->guc_id. Fixes: de1add360522c876c25ef2bbbbab1c94bdb509ab Cc: stable@vger.kernel.org # v4.6 Signed-off-by: Chris Wilson Cc: Tvrtko Ursulin Reviewed-by: Ville Syrjälä Cc: Ville Syrjälä Link: http://patchwork.freedesktop.org/patch/msgid/1461932305-14637-3-git-send-email-chris@chris-wilson.co.uk (cherry picked from commit 215a7e3210eb208abe634480741e418b5a8bf60c) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_ringbuffer.c | 9 +++++++-- drivers/gpu/drm/i915/intel_ringbuffer.h | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_ringbuffer.c b/drivers/gpu/drm/i915/intel_ringbuffer.c index fd93987b16f2..116023822bb9 100644 --- a/drivers/gpu/drm/i915/intel_ringbuffer.c +++ b/drivers/gpu/drm/i915/intel_ringbuffer.c @@ -1309,7 +1309,7 @@ static int gen8_rcs_signal(struct drm_i915_gem_request *signaller_req, intel_ring_emit(signaller, seqno); intel_ring_emit(signaller, 0); intel_ring_emit(signaller, MI_SEMAPHORE_SIGNAL | - MI_SEMAPHORE_TARGET(waiter->id)); + MI_SEMAPHORE_TARGET(waiter->hw_id)); intel_ring_emit(signaller, 0); } @@ -1349,7 +1349,7 @@ static int gen8_xcs_signal(struct drm_i915_gem_request *signaller_req, intel_ring_emit(signaller, upper_32_bits(gtt_offset)); intel_ring_emit(signaller, seqno); intel_ring_emit(signaller, MI_SEMAPHORE_SIGNAL | - MI_SEMAPHORE_TARGET(waiter->id)); + MI_SEMAPHORE_TARGET(waiter->hw_id)); intel_ring_emit(signaller, 0); } @@ -2778,6 +2778,7 @@ int intel_init_render_ring_buffer(struct drm_device *dev) engine->name = "render ring"; engine->id = RCS; engine->exec_id = I915_EXEC_RENDER; + engine->hw_id = 0; engine->mmio_base = RENDER_RING_BASE; if (INTEL_INFO(dev)->gen >= 8) { @@ -2929,6 +2930,7 @@ int intel_init_bsd_ring_buffer(struct drm_device *dev) engine->name = "bsd ring"; engine->id = VCS; engine->exec_id = I915_EXEC_BSD; + engine->hw_id = 1; engine->write_tail = ring_write_tail; if (INTEL_INFO(dev)->gen >= 6) { @@ -3007,6 +3009,7 @@ int intel_init_bsd2_ring_buffer(struct drm_device *dev) engine->name = "bsd2 ring"; engine->id = VCS2; engine->exec_id = I915_EXEC_BSD; + engine->hw_id = 4; engine->write_tail = ring_write_tail; engine->mmio_base = GEN8_BSD2_RING_BASE; @@ -3039,6 +3042,7 @@ int intel_init_blt_ring_buffer(struct drm_device *dev) engine->name = "blitter ring"; engine->id = BCS; engine->exec_id = I915_EXEC_BLT; + engine->hw_id = 2; engine->mmio_base = BLT_RING_BASE; engine->write_tail = ring_write_tail; @@ -3098,6 +3102,7 @@ int intel_init_vebox_ring_buffer(struct drm_device *dev) engine->name = "video enhancement ring"; engine->id = VECS; engine->exec_id = I915_EXEC_VEBOX; + engine->hw_id = 3; engine->mmio_base = VEBOX_RING_BASE; engine->write_tail = ring_write_tail; diff --git a/drivers/gpu/drm/i915/intel_ringbuffer.h b/drivers/gpu/drm/i915/intel_ringbuffer.h index 2ade194bbea9..3adce83cfcfc 100644 --- a/drivers/gpu/drm/i915/intel_ringbuffer.h +++ b/drivers/gpu/drm/i915/intel_ringbuffer.h @@ -156,7 +156,8 @@ struct intel_engine_cs { #define I915_NUM_ENGINES 5 #define _VCS(n) (VCS + (n)) unsigned int exec_id; - unsigned int guc_id; + unsigned int hw_id; + unsigned int guc_id; /* XXX same as hw_id? */ u32 mmio_base; struct drm_device *dev; struct intel_ringbuffer *buffer; From 9f7e6f33d4ffe6ab0855e851d964b6aa68451161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 11 May 2016 22:04:33 +0300 Subject: [PATCH 192/393] drm/i915: s/DPPL/DPLL/ for SKL DPLLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SKL DPLLs shouldn't be called DPPLs. Cc: Ander Conselvan de Oliveira Fixes: 2edd6443e3d0 ("drm/i915: Use a table to initilize shared dplls") Signed-off-by: Ville Syrjälä Link: http://patchwork.freedesktop.org/patch/msgid/1462993473-8254-1-git-send-email-ville.syrjala@linux.intel.com Reviewed-by: Ander Conselvan de Oliveira (cherry picked from commit d5aab9d40135725cbe81ed5e3af5209382063193) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_dpll_mgr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_dpll_mgr.c b/drivers/gpu/drm/i915/intel_dpll_mgr.c index 639bf0209c15..3ac705936b04 100644 --- a/drivers/gpu/drm/i915/intel_dpll_mgr.c +++ b/drivers/gpu/drm/i915/intel_dpll_mgr.c @@ -1702,9 +1702,9 @@ static const struct intel_dpll_mgr hsw_pll_mgr = { static const struct dpll_info skl_plls[] = { { "DPLL 0", DPLL_ID_SKL_DPLL0, &skl_ddi_dpll0_funcs, INTEL_DPLL_ALWAYS_ON }, - { "DPPL 1", DPLL_ID_SKL_DPLL1, &skl_ddi_pll_funcs, 0 }, - { "DPPL 2", DPLL_ID_SKL_DPLL2, &skl_ddi_pll_funcs, 0 }, - { "DPPL 3", DPLL_ID_SKL_DPLL3, &skl_ddi_pll_funcs, 0 }, + { "DPLL 1", DPLL_ID_SKL_DPLL1, &skl_ddi_pll_funcs, 0 }, + { "DPLL 2", DPLL_ID_SKL_DPLL2, &skl_ddi_pll_funcs, 0 }, + { "DPLL 3", DPLL_ID_SKL_DPLL3, &skl_ddi_pll_funcs, 0 }, { NULL, -1, NULL, }, }; From 7045c3689f148a0c95f42bae8ef3eb2829ac7de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Fri, 13 May 2016 17:55:17 +0300 Subject: [PATCH 193/393] drm/i915: Don't leave old junk in ilk active watermarks on readout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we read out the watermark state from the hardware we're supposed to transfer that into the active watermarks, but currently we fail to any part of the active watermarks that isn't explicitly written. Let's clear it all upfront. Looks like this has been like this since the beginning, when I added the readout. No idea why I didn't clear it up. Cc: Matt Roper Fixes: 243e6a44b9ca ("drm/i915: Init HSW watermark tracking in intel_modeset_setup_hw_state()") Cc: stable@vger.kernel.org Signed-off-by: Ville Syrjälä Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: http://patchwork.freedesktop.org/patch/msgid/1463151318-14719-2-git-send-email-ville.syrjala@linux.intel.com (cherry picked from commit 15606534bf0a65d8a74a90fd57b8712d147dbca6) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_pm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c index 695a464a5e64..f3875bd78130 100644 --- a/drivers/gpu/drm/i915/intel_pm.c +++ b/drivers/gpu/drm/i915/intel_pm.c @@ -3904,6 +3904,8 @@ static void ilk_pipe_wm_get_hw_state(struct drm_crtc *crtc) if (IS_HASWELL(dev) || IS_BROADWELL(dev)) hw->wm_linetime[pipe] = I915_READ(PIPE_WM_LINETIME(pipe)); + memset(active, 0, sizeof(*active)); + active->pipe_enabled = intel_crtc->active; if (active->pipe_enabled) { From 4832aa16c4cdfc750e47c5752a32776f93b6448d Mon Sep 17 00:00:00 2001 From: Ramalingam C Date: Tue, 19 Apr 2016 13:48:14 +0530 Subject: [PATCH 194/393] drm/i915/bxt: Adjusting the error in horizontal timings retrieval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In BXT DSI there is no regs programmed with few horizontal timings in Pixels but txbyteclkhs.. So retrieval process adds some ROUND_UP ERRORS in the process of PIXELS<==>txbyteclkhs. Actually here for the given adjusted_mode, we are calculating the value programmed to the port and then back to the horizontal timing param in pixels. This is the expected value at the end of get_config, including roundup errors. And if that is same as retrieved value from port, then retrieved (HW state) adjusted_mode's horizontal timings are corrected to match with SW state to nullify the errors. Signed-off-by: Ramalingam C Acked-by: Daniel Vetter Acked-by: Ville Syrjälä Signed-off-by: Jani Nikula Link: http://patchwork.freedesktop.org/patch/msgid/1461053894-5058-2-git-send-email-ramalingam.c@intel.com (cherry picked from commit 042ab0c3c40be1efcaad6b526173b5536fc6c3bf) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_dsi.c | 97 +++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_dsi.c b/drivers/gpu/drm/i915/intel_dsi.c index efa306bb19c9..366ad6c67ce4 100644 --- a/drivers/gpu/drm/i915/intel_dsi.c +++ b/drivers/gpu/drm/i915/intel_dsi.c @@ -46,6 +46,14 @@ static const struct { }, }; +/* return pixels in terms of txbyteclkhs */ +static u16 txbyteclkhs(u16 pixels, int bpp, int lane_count, + u16 burst_mode_ratio) +{ + return DIV_ROUND_UP(DIV_ROUND_UP(pixels * bpp * burst_mode_ratio, + 8 * 100), lane_count); +} + /* return pixels equvalent to txbyteclkhs */ static u16 pixels_from_txbyteclkhs(u16 clk_hs, int bpp, int lane_count, u16 burst_mode_ratio) @@ -788,11 +796,19 @@ static void bxt_dsi_get_pipe_config(struct intel_encoder *encoder, struct drm_i915_private *dev_priv = dev->dev_private; struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; + struct drm_display_mode *adjusted_mode_sw; + struct intel_crtc *intel_crtc; struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); unsigned int lane_count = intel_dsi->lane_count; unsigned int bpp, fmt; enum port port; u16 hactive, hfp, hsync, hbp, vfp, vsync, vbp; + u16 hfp_sw, hsync_sw, hbp_sw; + u16 crtc_htotal_sw, crtc_hsync_start_sw, crtc_hsync_end_sw, + crtc_hblank_start_sw, crtc_hblank_end_sw; + + intel_crtc = to_intel_crtc(encoder->base.crtc); + adjusted_mode_sw = &intel_crtc->config->base.adjusted_mode; /* * Atleast one port is active as encoder->get_config called only if @@ -856,8 +872,79 @@ static void bxt_dsi_get_pipe_config(struct intel_encoder *encoder, adjusted_mode->crtc_vsync_end = vsync + adjusted_mode->crtc_vsync_start; adjusted_mode->crtc_vblank_start = adjusted_mode->crtc_vdisplay; adjusted_mode->crtc_vblank_end = adjusted_mode->crtc_vtotal; -} + /* + * In BXT DSI there is no regs programmed with few horizontal timings + * in Pixels but txbyteclkhs.. So retrieval process adds some + * ROUND_UP ERRORS in the process of PIXELS<==>txbyteclkhs. + * Actually here for the given adjusted_mode, we are calculating the + * value programmed to the port and then back to the horizontal timing + * param in pixels. This is the expected value, including roundup errors + * And if that is same as retrieved value from port, then + * (HW state) adjusted_mode's horizontal timings are corrected to + * match with SW state to nullify the errors. + */ + /* Calculating the value programmed to the Port register */ + hfp_sw = adjusted_mode_sw->crtc_hsync_start - + adjusted_mode_sw->crtc_hdisplay; + hsync_sw = adjusted_mode_sw->crtc_hsync_end - + adjusted_mode_sw->crtc_hsync_start; + hbp_sw = adjusted_mode_sw->crtc_htotal - + adjusted_mode_sw->crtc_hsync_end; + + if (intel_dsi->dual_link) { + hfp_sw /= 2; + hsync_sw /= 2; + hbp_sw /= 2; + } + + hfp_sw = txbyteclkhs(hfp_sw, bpp, lane_count, + intel_dsi->burst_mode_ratio); + hsync_sw = txbyteclkhs(hsync_sw, bpp, lane_count, + intel_dsi->burst_mode_ratio); + hbp_sw = txbyteclkhs(hbp_sw, bpp, lane_count, + intel_dsi->burst_mode_ratio); + + /* Reverse calculating the adjusted mode parameters from port reg vals*/ + hfp_sw = pixels_from_txbyteclkhs(hfp_sw, bpp, lane_count, + intel_dsi->burst_mode_ratio); + hsync_sw = pixels_from_txbyteclkhs(hsync_sw, bpp, lane_count, + intel_dsi->burst_mode_ratio); + hbp_sw = pixels_from_txbyteclkhs(hbp_sw, bpp, lane_count, + intel_dsi->burst_mode_ratio); + + if (intel_dsi->dual_link) { + hfp_sw *= 2; + hsync_sw *= 2; + hbp_sw *= 2; + } + + crtc_htotal_sw = adjusted_mode_sw->crtc_hdisplay + hfp_sw + + hsync_sw + hbp_sw; + crtc_hsync_start_sw = hfp_sw + adjusted_mode_sw->crtc_hdisplay; + crtc_hsync_end_sw = hsync_sw + crtc_hsync_start_sw; + crtc_hblank_start_sw = adjusted_mode_sw->crtc_hdisplay; + crtc_hblank_end_sw = crtc_htotal_sw; + + if (adjusted_mode->crtc_htotal == crtc_htotal_sw) + adjusted_mode->crtc_htotal = adjusted_mode_sw->crtc_htotal; + + if (adjusted_mode->crtc_hsync_start == crtc_hsync_start_sw) + adjusted_mode->crtc_hsync_start = + adjusted_mode_sw->crtc_hsync_start; + + if (adjusted_mode->crtc_hsync_end == crtc_hsync_end_sw) + adjusted_mode->crtc_hsync_end = + adjusted_mode_sw->crtc_hsync_end; + + if (adjusted_mode->crtc_hblank_start == crtc_hblank_start_sw) + adjusted_mode->crtc_hblank_start = + adjusted_mode_sw->crtc_hblank_start; + + if (adjusted_mode->crtc_hblank_end == crtc_hblank_end_sw) + adjusted_mode->crtc_hblank_end = + adjusted_mode_sw->crtc_hblank_end; +} static void intel_dsi_get_config(struct intel_encoder *encoder, struct intel_crtc_state *pipe_config) @@ -921,14 +1008,6 @@ static u16 txclkesc(u32 divider, unsigned int us) } } -/* return pixels in terms of txbyteclkhs */ -static u16 txbyteclkhs(u16 pixels, int bpp, int lane_count, - u16 burst_mode_ratio) -{ - return DIV_ROUND_UP(DIV_ROUND_UP(pixels * bpp * burst_mode_ratio, - 8 * 100), lane_count); -} - static void set_dsi_timings(struct drm_encoder *encoder, const struct drm_display_mode *adjusted_mode) { From 310944d148e3600dcff8b346bee7fa01d34903b1 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 12 May 2016 15:00:44 +0200 Subject: [PATCH 195/393] drm/imx: Match imx-ipuv3-crtc components using device node in platform data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The component master driver imx-drm-core matches component devices using their of_node. Since commit 950b410dd1ab ("gpu: ipu-v3: Fix imx-ipuv3-crtc module autoloading"), the imx-ipuv3-crtc dev->of_node is not set during probing. Before that, of_node was set and caused an of: modalias to be used instead of the platform: modalias, which broke module autoloading. On the other hand, if dev->of_node is not set yet when the imx-ipuv3-crtc probe function calls component_add, component matching in imx-drm-core fails. While dev->of_node will be set once the next component tries to bring up the component master, imx-drm-core component binding will never succeed if one of the crtc devices is probed last. Add of_node to the component platform data and match against the pdata->of_node instead of dev->of_node in imx-drm-core to work around this problem. Cc: # 4.4.x Fixes: 950b410dd1ab ("gpu: ipu-v3: Fix imx-ipuv3-crtc module autoloading") Signed-off-by: Philipp Zabel Tested-by: Fabio Estevam Tested-by: Lothar Waßmann Tested-by: Heiko Schocher Tested-by: Chris Ruehl --- drivers/gpu/drm/imx/imx-drm-core.c | 8 ++++++++ drivers/gpu/drm/imx/ipuv3-crtc.c | 2 +- drivers/gpu/ipu-v3/ipu-common.c | 5 +++-- include/video/imx-ipu-v3.h | 2 ++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index e26dcdec2aba..05229b960e0c 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -25,6 +25,7 @@ #include #include #include +#include