linux_dsm_epyc7002/drivers/gpu/drm/i915/intel_lpe_audio.c
Chris Wilson dde7b00e4c drm/i915: Fix use after free in lpe_audio_platdev_destroy()
[31908.547136] BUG: KASAN: use-after-free in intel_lpe_audio_teardown+0x78/0xb0 [i915] at addr ffff8801f7788358
[31908.547297] Read of size 8 by task drv_selftest/3781
[31908.547405] CPU: 0 PID: 3781 Comm: drv_selftest Tainted: G    BU  W       4.10.0+ #451
[31908.547553] Hardware name:                  /        , BIOS PYBSWCEL.86A.0027.2015.0507.1758 05/07/2015
[31908.547682] Call Trace:
[31908.547772]  dump_stack+0x68/0x9f
[31908.547857]  kasan_object_err+0x1c/0x70
[31908.547947]  kasan_report_error+0x1f1/0x4f0
[31908.548038]  ? kfree+0xaa/0x170
[31908.548121]  kasan_report+0x34/0x40
[31908.548211]  ? klist_children_get+0x20/0x30
[31908.548472]  ? intel_lpe_audio_teardown+0x78/0xb0 [i915]
[31908.548567]  __asan_load8+0x5e/0x70
[31908.548824]  intel_lpe_audio_teardown+0x78/0xb0 [i915]
[31908.549080]  intel_audio_deinit+0x28/0x80 [i915]
[31908.549315]  i915_driver_unload+0xe4/0x360 [i915]
[31908.549551]  ? i915_driver_load+0x1d70/0x1d70 [i915]
[31908.549651]  ? trace_hardirqs_on+0xd/0x10
[31908.549885]  i915_pci_remove+0x23/0x30 [i915]
[31908.549978]  pci_device_remove+0x5c/0x100
[31908.550069]  device_release_driver_internal+0x1db/0x2e0
[31908.550165]  driver_detach+0x68/0xc0
[31908.550256]  bus_remove_driver+0x8b/0x150
[31908.550346]  driver_unregister+0x3e/0x60
[31908.550439]  pci_unregister_driver+0x1d/0x110
[31908.550531]  ? find_module_all+0x7a/0xa0
[31908.550791]  i915_exit+0x1a/0x87 [i915]
[31908.550881]  SyS_delete_module+0x264/0x2c0
[31908.550971]  ? free_module+0x430/0x430
[31908.551064]  ? trace_hardirqs_off_caller+0x16/0x110
[31908.551159]  ? trace_hardirqs_on_caller+0x16/0x280
[31908.551256]  ? trace_hardirqs_on_thunk+0x1a/0x1c
[31908.551350]  entry_SYSCALL_64_fastpath+0x1c/0xb1
[31908.551440] RIP: 0033:0x7f1d67312ec7
[31908.551520] RSP: 002b:00007ffebe34e888 EFLAGS: 00000206 ORIG_RAX: 00000000000000b0
[31908.551650] RAX: ffffffffffffffda RBX: ffffffff811123f6 RCX: 00007f1d67312ec7
[31908.551743] RDX: 0000000000000000 RSI: 0000000000000800 RDI: 0000560d0af476b8
[31908.551837] RBP: ffff880233d87f98 R08: 0000000000000000 R09: 00007ffebe34e8b8
[31908.551930] R10: 00007f1d68adf8c0 R11: 0000000000000206 R12: 0000000000000000
[31908.552023] R13: 0000560d0af46440 R14: 0000000000000034 R15: 00007ffebe34d860
[31908.552121]  ? trace_hardirqs_off_caller+0x16/0x110
[31908.552217] Object at ffff8801f7788000, in cache kmalloc-2048 size: 2048
[31908.552306] Allocated:
[31908.552377] PID = 3781
[31908.552456]  save_stack_trace+0x16/0x20
[31908.552539]  kasan_kmalloc+0xee/0x190
[31908.552627]  __kmalloc+0xdb/0x1b0
[31908.552713]  platform_device_alloc+0x27/0x90
[31908.552804]  platform_device_register_full+0x36/0x220
[31908.553066]  intel_lpe_audio_init+0x41e/0x570 [i915]
[31908.553320]  intel_audio_init+0xd/0x40 [i915]
[31908.553552]  i915_driver_load+0x13f5/0x1d70 [i915]
[31908.553788]  i915_pci_probe+0x65/0xe0 [i915]
[31908.553881]  pci_device_probe+0xda/0x140
[31908.553969]  driver_probe_device+0x400/0x660
[31908.554058]  __driver_attach+0x11c/0x120
[31908.554147]  bus_for_each_dev+0xe6/0x150
[31908.554237]  driver_attach+0x26/0x30
[31908.554325]  bus_add_driver+0x26b/0x3b0
[31908.554412]  driver_register+0xce/0x190
[31908.554502]  __pci_register_driver+0xaf/0xc0
[31908.554589]  0xffffffffa0550063
[31908.554675]  do_one_initcall+0x8b/0x1e0
[31908.554764]  do_init_module+0x102/0x325
[31908.554852]  load_module+0x3aad/0x45e0
[31908.554944]  SyS_finit_module+0x169/0x1a0
[31908.555033]  entry_SYSCALL_64_fastpath+0x1c/0xb1
[31908.555119] Freed:
[31908.555188] PID = 3781
[31908.555266]  save_stack_trace+0x16/0x20
[31908.555349]  kasan_slab_free+0xb0/0x180
[31908.555436]  kfree+0xaa/0x170
[31908.555520]  platform_device_release+0x76/0x80
[31908.555610]  device_release+0x45/0xe0
[31908.555698]  kobject_put+0x11f/0x260
[31908.555785]  put_device+0x12/0x20
[31908.555871]  platform_device_unregister+0x1b/0x20
[31908.556135]  intel_lpe_audio_teardown+0x5c/0xb0 [i915]
[31908.556390]  intel_audio_deinit+0x28/0x80 [i915]
[31908.556622]  i915_driver_unload+0xe4/0x360 [i915]
[31908.556858]  i915_pci_remove+0x23/0x30 [i915]
[31908.556948]  pci_device_remove+0x5c/0x100
[31908.557037]  device_release_driver_internal+0x1db/0x2e0
[31908.557129]  driver_detach+0x68/0xc0
[31908.557217]  bus_remove_driver+0x8b/0x150
[31908.557304]  driver_unregister+0x3e/0x60
[31908.557394]  pci_unregister_driver+0x1d/0x110
[31908.557653]  i915_exit+0x1a/0x87 [i915]
[31908.557741]  SyS_delete_module+0x264/0x2c0
[31908.557834]  entry_SYSCALL_64_fastpath+0x1c/0xb1
[31908.557919] Memory state around the buggy address:
[31908.558005]  ffff8801f7788200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[31908.558127]  ffff8801f7788280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[31908.558255] >ffff8801f7788300: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[31908.558374]                                                     ^
[31908.558467]  ffff8801f7788380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[31908.558595]  ffff8801f7788400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

v2: Just leak the memory (8 bytes) as freeing it ourselves is not safe,
and we need to coordinate a proper fix in platform_device itself.

Fixes: eef57324d9 ("drm/i915: setup bridge for HDMI LPE audio driver")
Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=99952
Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Cc: Jerome Anand <jerome.anand@intel.com>
Cc: Jani Nikula <jani.nikula@intel.com>
Cc: Takashi Iwai <tiwai@suse.de>
Link: http://patchwork.freedesktop.org/patch/msgid/20170412080251.30648-1-chris@chris-wilson.co.uk
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
(cherry picked from commit 48ae80741d)
Signed-off-by: Jani Nikula <jani.nikula@intel.com>
2017-04-26 16:28:15 +03:00

401 lines
11 KiB
C

/*
* 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 (including the next
* paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
*
* Authors:
* Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
* Jerome Anand <jerome.anand@intel.com>
* based on VED patches
*
*/
/**
* DOC: LPE Audio integration for HDMI or DP playback
*
* Motivation:
* Atom platforms (e.g. valleyview and cherryTrail) integrates a DMA-based
* interface as an alternative to the traditional HDaudio path. While this
* mode is unrelated to the LPE aka SST audio engine, the documentation refers
* to this mode as LPE so we keep this notation for the sake of consistency.
*
* The interface is handled by a separate standalone driver maintained in the
* ALSA subsystem for simplicity. To minimize the interaction between the two
* subsystems, a bridge is setup between the hdmi-lpe-audio and i915:
* 1. Create a platform device to share MMIO/IRQ resources
* 2. Make the platform device child of i915 device for runtime PM.
* 3. Create IRQ chip to forward the LPE audio irqs.
* the hdmi-lpe-audio driver probes the lpe audio device and creates a new
* sound card
*
* Threats:
* Due to the restriction in Linux platform device model, user need manually
* uninstall the hdmi-lpe-audio driver before uninstalling i915 module,
* otherwise we might run into use-after-free issues after i915 removes the
* platform device: even though hdmi-lpe-audio driver is released, the modules
* is still in "installed" status.
*
* Implementation:
* The MMIO/REG platform resources are created according to the registers
* specification.
* When forwarding LPE audio irqs, the flow control handler selection depends
* on the platform, for example on valleyview handle_simple_irq is enough.
*
*/
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/pci.h>
#include "i915_drv.h"
#include <linux/delay.h>
#include <drm/intel_lpe_audio.h>
#define HAS_LPE_AUDIO(dev_priv) ((dev_priv)->lpe_audio.platdev != NULL)
static struct platform_device *
lpe_audio_platdev_create(struct drm_i915_private *dev_priv)
{
int ret;
struct drm_device *dev = &dev_priv->drm;
struct platform_device_info pinfo = {};
struct resource *rsc;
struct platform_device *platdev;
struct intel_hdmi_lpe_audio_pdata *pdata;
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
rsc = kcalloc(2, sizeof(*rsc), GFP_KERNEL);
if (!rsc) {
kfree(pdata);
return ERR_PTR(-ENOMEM);
}
rsc[0].start = rsc[0].end = dev_priv->lpe_audio.irq;
rsc[0].flags = IORESOURCE_IRQ;
rsc[0].name = "hdmi-lpe-audio-irq";
rsc[1].start = pci_resource_start(dev->pdev, 0) +
I915_HDMI_LPE_AUDIO_BASE;
rsc[1].end = pci_resource_start(dev->pdev, 0) +
I915_HDMI_LPE_AUDIO_BASE + I915_HDMI_LPE_AUDIO_SIZE - 1;
rsc[1].flags = IORESOURCE_MEM;
rsc[1].name = "hdmi-lpe-audio-mmio";
pinfo.parent = dev->dev;
pinfo.name = "hdmi-lpe-audio";
pinfo.id = -1;
pinfo.res = rsc;
pinfo.num_res = 2;
pinfo.data = pdata;
pinfo.size_data = sizeof(*pdata);
pinfo.dma_mask = DMA_BIT_MASK(32);
spin_lock_init(&pdata->lpe_audio_slock);
platdev = platform_device_register_full(&pinfo);
if (IS_ERR(platdev)) {
ret = PTR_ERR(platdev);
DRM_ERROR("Failed to allocate LPE audio platform device\n");
goto err;
}
kfree(rsc);
return platdev;
err:
kfree(rsc);
kfree(pdata);
return ERR_PTR(ret);
}
static void lpe_audio_platdev_destroy(struct drm_i915_private *dev_priv)
{
/* XXX Note that platform_device_register_full() allocates a dma_mask
* and never frees it. We can't free it here as we cannot guarantee
* this is the last reference (i.e. that the dma_mask will not be
* used after our unregister). So ee choose to leak the sizeof(u64)
* allocation here - it should be fixed in the platform_device rather
* than us fiddle with its internals.
*/
platform_device_unregister(dev_priv->lpe_audio.platdev);
}
static void lpe_audio_irq_unmask(struct irq_data *d)
{
struct drm_i915_private *dev_priv = d->chip_data;
unsigned long irqflags;
u32 val = (I915_LPE_PIPE_A_INTERRUPT |
I915_LPE_PIPE_B_INTERRUPT);
if (IS_CHERRYVIEW(dev_priv))
val |= I915_LPE_PIPE_C_INTERRUPT;
spin_lock_irqsave(&dev_priv->irq_lock, irqflags);
dev_priv->irq_mask &= ~val;
I915_WRITE(VLV_IIR, val);
I915_WRITE(VLV_IIR, val);
I915_WRITE(VLV_IMR, dev_priv->irq_mask);
POSTING_READ(VLV_IMR);
spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags);
}
static void lpe_audio_irq_mask(struct irq_data *d)
{
struct drm_i915_private *dev_priv = d->chip_data;
unsigned long irqflags;
u32 val = (I915_LPE_PIPE_A_INTERRUPT |
I915_LPE_PIPE_B_INTERRUPT);
if (IS_CHERRYVIEW(dev_priv))
val |= I915_LPE_PIPE_C_INTERRUPT;
spin_lock_irqsave(&dev_priv->irq_lock, irqflags);
dev_priv->irq_mask |= val;
I915_WRITE(VLV_IMR, dev_priv->irq_mask);
I915_WRITE(VLV_IIR, val);
I915_WRITE(VLV_IIR, val);
POSTING_READ(VLV_IIR);
spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags);
}
static struct irq_chip lpe_audio_irqchip = {
.name = "hdmi_lpe_audio_irqchip",
.irq_mask = lpe_audio_irq_mask,
.irq_unmask = lpe_audio_irq_unmask,
};
static int lpe_audio_irq_init(struct drm_i915_private *dev_priv)
{
int irq = dev_priv->lpe_audio.irq;
WARN_ON(!intel_irqs_enabled(dev_priv));
irq_set_chip_and_handler_name(irq,
&lpe_audio_irqchip,
handle_simple_irq,
"hdmi_lpe_audio_irq_handler");
return irq_set_chip_data(irq, dev_priv);
}
static bool lpe_audio_detect(struct drm_i915_private *dev_priv)
{
int lpe_present = false;
if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) {
static const struct pci_device_id atom_hdaudio_ids[] = {
/* Baytrail */
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0f04)},
/* Braswell */
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2284)},
{}
};
if (!pci_dev_present(atom_hdaudio_ids)) {
DRM_INFO("%s\n", "HDaudio controller not detected, using LPE audio instead\n");
lpe_present = true;
}
}
return lpe_present;
}
static int lpe_audio_setup(struct drm_i915_private *dev_priv)
{
int ret;
dev_priv->lpe_audio.irq = irq_alloc_desc(0);
if (dev_priv->lpe_audio.irq < 0) {
DRM_ERROR("Failed to allocate IRQ desc: %d\n",
dev_priv->lpe_audio.irq);
ret = dev_priv->lpe_audio.irq;
goto err;
}
DRM_DEBUG("irq = %d\n", dev_priv->lpe_audio.irq);
ret = lpe_audio_irq_init(dev_priv);
if (ret) {
DRM_ERROR("Failed to initialize irqchip for lpe audio: %d\n",
ret);
goto err_free_irq;
}
dev_priv->lpe_audio.platdev = lpe_audio_platdev_create(dev_priv);
if (IS_ERR(dev_priv->lpe_audio.platdev)) {
ret = PTR_ERR(dev_priv->lpe_audio.platdev);
DRM_ERROR("Failed to create lpe audio platform device: %d\n",
ret);
goto err_free_irq;
}
/* enable chicken bit; at least this is required for Dell Wyse 3040
* with DP outputs (but only sometimes by some reason!)
*/
I915_WRITE(VLV_AUD_CHICKEN_BIT_REG, VLV_CHICKEN_BIT_DBG_ENABLE);
return 0;
err_free_irq:
irq_free_desc(dev_priv->lpe_audio.irq);
err:
dev_priv->lpe_audio.irq = -1;
dev_priv->lpe_audio.platdev = NULL;
return ret;
}
/**
* intel_lpe_audio_irq_handler() - forwards the LPE audio irq
* @dev_priv: the i915 drm device private data
*
* the LPE Audio irq is forwarded to the irq handler registered by LPE audio
* driver.
*/
void intel_lpe_audio_irq_handler(struct drm_i915_private *dev_priv)
{
int ret;
if (!HAS_LPE_AUDIO(dev_priv))
return;
ret = generic_handle_irq(dev_priv->lpe_audio.irq);
if (ret)
DRM_ERROR_RATELIMITED("error handling LPE audio irq: %d\n",
ret);
}
/**
* intel_lpe_audio_init() - detect and setup the bridge between HDMI LPE Audio
* driver and i915
* @dev_priv: the i915 drm device private data
*
* Return: 0 if successful. non-zero if detection or
* llocation/initialization fails
*/
int intel_lpe_audio_init(struct drm_i915_private *dev_priv)
{
int ret = -ENODEV;
if (lpe_audio_detect(dev_priv)) {
ret = lpe_audio_setup(dev_priv);
if (ret < 0)
DRM_ERROR("failed to setup LPE Audio bridge\n");
}
return ret;
}
/**
* intel_lpe_audio_teardown() - destroy the bridge between HDMI LPE
* audio driver and i915
* @dev_priv: the i915 drm device private data
*
* release all the resources for LPE audio <-> i915 bridge.
*/
void intel_lpe_audio_teardown(struct drm_i915_private *dev_priv)
{
struct irq_desc *desc;
if (!HAS_LPE_AUDIO(dev_priv))
return;
desc = irq_to_desc(dev_priv->lpe_audio.irq);
lpe_audio_irq_mask(&desc->irq_data);
lpe_audio_platdev_destroy(dev_priv);
irq_free_desc(dev_priv->lpe_audio.irq);
}
/**
* intel_lpe_audio_notify() - notify lpe audio event
* audio driver and i915
* @dev_priv: the i915 drm device private data
* @eld : ELD data
* @pipe: pipe id
* @port: port id
* @tmds_clk_speed: tmds clock frequency in Hz
*
* Notify lpe audio driver of eld change.
*/
void intel_lpe_audio_notify(struct drm_i915_private *dev_priv,
void *eld, int port, int pipe, int tmds_clk_speed,
bool dp_output, int link_rate)
{
unsigned long irq_flags;
struct intel_hdmi_lpe_audio_pdata *pdata = NULL;
u32 audio_enable;
if (!HAS_LPE_AUDIO(dev_priv))
return;
pdata = dev_get_platdata(
&(dev_priv->lpe_audio.platdev->dev));
spin_lock_irqsave(&pdata->lpe_audio_slock, irq_flags);
audio_enable = I915_READ(VLV_AUD_PORT_EN_DBG(port));
if (eld != NULL) {
memcpy(pdata->eld.eld_data, eld,
HDMI_MAX_ELD_BYTES);
pdata->eld.port_id = port;
pdata->eld.pipe_id = pipe;
pdata->hdmi_connected = true;
pdata->dp_output = dp_output;
if (tmds_clk_speed)
pdata->tmds_clock_speed = tmds_clk_speed;
if (link_rate)
pdata->link_rate = link_rate;
/* Unmute the amp for both DP and HDMI */
I915_WRITE(VLV_AUD_PORT_EN_DBG(port),
audio_enable & ~VLV_AMP_MUTE);
} else {
memset(pdata->eld.eld_data, 0,
HDMI_MAX_ELD_BYTES);
pdata->hdmi_connected = false;
pdata->dp_output = false;
/* Mute the amp for both DP and HDMI */
I915_WRITE(VLV_AUD_PORT_EN_DBG(port),
audio_enable | VLV_AMP_MUTE);
}
if (pdata->notify_audio_lpe)
pdata->notify_audio_lpe(dev_priv->lpe_audio.platdev);
else
pdata->notify_pending = true;
spin_unlock_irqrestore(&pdata->lpe_audio_slock,
irq_flags);
}