linux-loongson/drivers/gpu/drm/display/drm_hdmi_cec_helper.c
Cristian Ciocaltea 19920ab98e drm/display: hdmi-cec-helper: Fix adapter unregistration
Attempting to reload a kernel module of an HDMI driver making use of the
new CEC helpers revealed a resource deallocation issue, i.e. the entries
in /dev/cec* keep growing.

Moreover, after a couple of tries the kernel crashes and the whole
system freezes:

[   47.515950] Unable to handle kernel paging request at virtual address 0020072007200778
[...]
[   47.521707] Internal error: Oops: 0000000096000004 [#1]  SMP
[...]
[   47.537597] Call trace:
[   47.537815]  klist_next+0x20/0x1b8 (P)
[   47.538152]  device_reorder_to_tail+0x74/0x120
[   47.538548]  device_reorder_to_tail+0x6c/0x120
[   47.538944]  device_pm_move_to_tail+0x78/0xd0
[   47.539334]  deferred_probe_work_func+0x9c/0x110
[   47.539747]  process_one_work+0x328/0x638
[   47.540108]  worker_thread+0x264/0x390
[   47.540445]  kthread+0x20c/0x230
[   47.540735]  ret_from_fork+0x10/0x20

Do a proper cleanup by calling cec_unregister_adapter() instead of
cec_delete_adapter() in the managed release action handler.

Fixes: 8b1a8f8b20 ("drm/display: add CEC helpers code")
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20250703-hdmi-cec-helper-unreg-fix-v1-1-7e7b0eb578bb@collabora.com
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-07-05 16:18:54 +03:00

194 lines
5.4 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024 Linaro Ltd
*/
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_managed.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <linux/export.h>
#include <linux/mutex.h>
#include <media/cec.h>
struct drm_connector_hdmi_cec_data {
struct cec_adapter *adapter;
const struct drm_connector_hdmi_cec_funcs *funcs;
};
static int drm_connector_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->enable(connector, enable);
}
static int drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->log_addr(connector, logical_addr);
}
static int drm_connector_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct drm_connector *connector = cec_get_drvdata(adap);
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
return data->funcs->transmit(connector, attempts, signal_free_time, msg);
}
static const struct cec_adap_ops drm_connector_hdmi_cec_adap_ops = {
.adap_enable = drm_connector_hdmi_cec_adap_enable,
.adap_log_addr = drm_connector_hdmi_cec_adap_log_addr,
.adap_transmit = drm_connector_hdmi_cec_adap_transmit,
};
static void drm_connector_hdmi_cec_adapter_phys_addr_invalidate(struct drm_connector *connector)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_phys_addr_invalidate(data->adapter);
}
static void drm_connector_hdmi_cec_adapter_phys_addr_set(struct drm_connector *connector,
u16 addr)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_s_phys_addr(data->adapter, addr, false);
}
static void drm_connector_hdmi_cec_adapter_unregister(struct drm_device *dev, void *res)
{
struct drm_connector *connector = res;
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_unregister_adapter(data->adapter);
if (data->funcs->uninit)
data->funcs->uninit(connector);
kfree(data);
connector->cec.data = NULL;
}
static struct drm_connector_cec_funcs drm_connector_hdmi_cec_adapter_funcs = {
.phys_addr_invalidate = drm_connector_hdmi_cec_adapter_phys_addr_invalidate,
.phys_addr_set = drm_connector_hdmi_cec_adapter_phys_addr_set,
};
int drmm_connector_hdmi_cec_register(struct drm_connector *connector,
const struct drm_connector_hdmi_cec_funcs *funcs,
const char *name,
u8 available_las,
struct device *dev)
{
struct drm_connector_hdmi_cec_data *data;
struct cec_connector_info conn_info;
struct cec_adapter *cec_adap;
int ret;
if (!funcs->init || !funcs->enable || !funcs->log_addr || !funcs->transmit)
return -EINVAL;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->funcs = funcs;
cec_adap = cec_allocate_adapter(&drm_connector_hdmi_cec_adap_ops, connector, name,
CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO,
available_las ? : CEC_MAX_LOG_ADDRS);
ret = PTR_ERR_OR_ZERO(cec_adap);
if (ret < 0)
goto err_free;
cec_fill_conn_info_from_drm(&conn_info, connector);
cec_s_conn_info(cec_adap, &conn_info);
data->adapter = cec_adap;
mutex_lock(&connector->cec.mutex);
connector->cec.data = data;
connector->cec.funcs = &drm_connector_hdmi_cec_adapter_funcs;
ret = funcs->init(connector);
if (ret < 0)
goto err_delete_adapter;
/*
* NOTE: the CEC adapter will be unregistered by drmm cleanup from
* drm_managed_release(), which is called from drm_dev_release()
* during device unbind.
*
* However, the CEC framework cleans up the CEC adapter only when the
* last user has closed its file descriptor, so we don't need to handle
* it in DRM.
*
* Before that CEC framework makes sure that even if the userspace
* still holds CEC device open, all calls will be shortcut via
* cec_is_registered(), making sure that there is no access to the
* freed memory.
*/
ret = cec_register_adapter(cec_adap, dev);
if (ret < 0)
goto err_delete_adapter;
mutex_unlock(&connector->cec.mutex);
return drmm_add_action_or_reset(connector->dev,
drm_connector_hdmi_cec_adapter_unregister,
connector);
err_delete_adapter:
cec_delete_adapter(cec_adap);
connector->cec.data = NULL;
mutex_unlock(&connector->cec.mutex);
err_free:
kfree(data);
return ret;
}
EXPORT_SYMBOL(drmm_connector_hdmi_cec_register);
void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
struct cec_msg *msg)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_received_msg(data->adapter, msg);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg);
void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector,
u8 status)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_transmit_attempt_done(data->adapter, status);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done);
void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
u8 status,
u8 arb_lost_cnt, u8 nack_cnt,
u8 low_drive_cnt, u8 error_cnt)
{
struct drm_connector_hdmi_cec_data *data = connector->cec.data;
cec_transmit_done(data->adapter, status,
arb_lost_cnt, nack_cnt, low_drive_cnt, error_cnt);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);