From: Greg Kroah-Hartman Date: Fri, 7 Aug 2026 12:11:05 +0000 (+0200) Subject: 6.18-stable patches X-Git-Tag: v6.6.151~28 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d01cab45c0f263d5f65de36b4b6411c7533f2b30;p=thirdparty%2Fkernel%2Fstable-queue.git 6.18-stable patches added patches: media-chips-media-wave5-support-cbp-profile.patch media-qcom-camss-csid-340-fix-unused-variables.patch media-qcom-camss-fix-rdi-streaming-for-csid-340.patch media-uapi-rkisp-correct-name-version-enum.patch usb-typec-ucsi-fix-race-condition-and-ordering-in-port-unregistration.patch usb-typec-ucsi-split-connector-lock-classes.patch userfaultfd-prevent-registration-of-special-vmas.patch wifi-brcmfmac-drain-bus_reset-work-on-device-removal.patch --- diff --git a/queue-6.18/media-chips-media-wave5-support-cbp-profile.patch b/queue-6.18/media-chips-media-wave5-support-cbp-profile.patch new file mode 100644 index 0000000000..520828d4c5 --- /dev/null +++ b/queue-6.18/media-chips-media-wave5-support-cbp-profile.patch @@ -0,0 +1,82 @@ +From stable+bounces-295111-greg=kroah.com@vger.kernel.org Tue Aug 4 13:01:09 2026 +From: Sasha Levin +Date: Tue, 4 Aug 2026 06:47:12 -0400 +Subject: media: chips-media: wave5: Support CBP profile +To: stable@vger.kernel.org +Cc: Jackson Lee , Nas Chung , Brandon Brnich , Nicolas Dufresne , Hans Verkuil , Sasha Levin +Message-ID: <20260804104712.2825343-1-sashal@kernel.org> + +From: Jackson Lee + +[ Upstream commit f8505f9d6b223a26854003bfdba1a0772457886d ] + +Constrained Baseline Profile (CBP) and Baseline Profile (BP) have been +treated as the same. +Introduce the ability to differentiate between the two. + +Fixes: 9707a6254a8a ("media: chips-media: wave5: Add the v4l2 layer") +Cc: stable@vger.kernel.org +Signed-off-by: Jackson Lee +Signed-off-by: Nas Chung +Tested-by: Brandon Brnich +Reviewed-by: Nicolas Dufresne +Signed-off-by: Nicolas Dufresne +Signed-off-by: Hans Verkuil +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/platform/chips-media/wave5/wave5-hw.c | 3 +++ + drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c | 5 ++++- + drivers/media/platform/chips-media/wave5/wave5-vpuapi.h | 1 + + 3 files changed, 8 insertions(+), 1 deletion(-) + +--- a/drivers/media/platform/chips-media/wave5/wave5-hw.c ++++ b/drivers/media/platform/chips-media/wave5/wave5-hw.c +@@ -1762,6 +1762,9 @@ int wave5_vpu_enc_init_seq(struct vpu_in + (p_param->skip_intra_trans << 25) | + (p_param->strong_intra_smooth_enable << 27) | + (p_param->en_still_picture << 30); ++ else if (inst->std == W_AVC_ENC) ++ reg_val |= (p_param->constraint_set1_flag << 29); ++ + vpu_write_reg(inst->dev, W5_CMD_ENC_SEQ_SPS_PARAM, reg_val); + + reg_val = (p_param->lossless_enable) | +--- a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c ++++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c +@@ -915,6 +915,8 @@ static int wave5_vpu_enc_s_ctrl(struct v + case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE: + inst->enc_param.profile = H264_PROFILE_BP; + inst->bit_depth = 8; ++ if (ctrl->val == V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) ++ inst->enc_param.constraint_set1_flag = 1; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + inst->enc_param.profile = H264_PROFILE_MP; +@@ -1190,6 +1192,7 @@ static int wave5_set_enc_openparam(struc + open_param->wave_param.intra_period = input.avc_idr_period; + } + } else { ++ open_param->wave_param.constraint_set1_flag = input.constraint_set1_flag; + open_param->wave_param.avc_idr_period = input.avc_idr_period; + } + open_param->wave_param.entropy_coding_mode = input.entropy_coding_mode; +@@ -1662,7 +1665,7 @@ static int wave5_vpu_open_enc(struct fil + -6, 6, 1, 0); + v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave5_vpu_enc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM, +- 0, 1, 1, 1); ++ 0, 1, 1, 0); + v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave5_vpu_enc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_CONSTRAINED_INTRA_PREDICTION, + 0, 1, 1, 0); +--- a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h ++++ b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h +@@ -570,6 +570,7 @@ struct enc_wave_param { + u32 transform8x8_enable: 1; /* enable 8x8 intra prediction and 8x8 transform */ + u32 mb_level_rc_enable: 1; /* enable MB-level rate control */ + u32 forced_idr_header_enable: 1; /* enable header encoding before IDR frame */ ++ u32 constraint_set1_flag: 1; /* enable CBP */ + }; + + struct enc_open_param { diff --git a/queue-6.18/media-qcom-camss-csid-340-fix-unused-variables.patch b/queue-6.18/media-qcom-camss-csid-340-fix-unused-variables.patch new file mode 100644 index 0000000000..32e6bc1d28 --- /dev/null +++ b/queue-6.18/media-qcom-camss-csid-340-fix-unused-variables.patch @@ -0,0 +1,71 @@ +From stable+bounces-295295-greg=kroah.com@vger.kernel.org Wed Aug 5 02:10:21 2026 +From: Sasha Levin +Date: Tue, 4 Aug 2026 20:08:32 -0400 +Subject: media: qcom: camss: csid-340: Fix unused variables +To: stable@vger.kernel.org +Cc: Loic Poulain , Bryan O'Donoghue , Vladimir Zapolskiy , Bryan O'Donoghue , Hans Verkuil , Sasha Levin +Message-ID: <20260805000833.3902619-1-sashal@kernel.org> + +From: Loic Poulain + +[ Upstream commit 0b61ce8dc4a71ea7baa19275c655677c612dde6d ] + +The CSID driver has some unused variables and function parameters +that are no longer needed (due to refactoring). Clean up those +unused elements: + +- Remove the `vc` parameter from `__csid_configure_rx()`. +- Drop the unused `lane_cnt` variable. +- Adjust call to `__csid_configure_rx()` accordingly. + +Signed-off-by: Loic Poulain +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Vladimir Zapolskiy +Signed-off-by: Bryan O'Donoghue +Signed-off-by: Hans Verkuil +Stable-dep-of: cc1c35619c98 ("media: qcom: camss: Fix RDI streaming for CSID 340") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/platform/qcom/camss/camss-csid-340.c | 10 +++------- + 1 file changed, 3 insertions(+), 7 deletions(-) + +--- a/drivers/media/platform/qcom/camss/camss-csid-340.c ++++ b/drivers/media/platform/qcom/camss/camss-csid-340.c +@@ -55,8 +55,7 @@ + #define CSID_RDI_CTRL_HALT_AT_FRAME_BOUNDARY 0 + #define CSID_RDI_CTRL_RESUME_AT_FRAME_BOUNDARY 1 + +-static void __csid_configure_rx(struct csid_device *csid, +- struct csid_phy_config *phy, int vc) ++static void __csid_configure_rx(struct csid_device *csid, struct csid_phy_config *phy) + { + u32 val; + +@@ -81,13 +80,9 @@ static void __csid_configure_rdi_stream( + const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats, + csid->res->formats->nformats, + input_format->code); +- u8 lane_cnt = csid->phy.lane_cnt; + u8 dt_id; + u32 val; + +- if (!lane_cnt) +- lane_cnt = 4; +- + /* + * DT_ID is a two bit bitfield that is concatenated with + * the four least significant bits of the five bit VC +@@ -120,10 +115,11 @@ static void csid_configure_stream(struct + { + int i; + ++ __csid_configure_rx(csid, &csid->phy); ++ + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) { + if (csid->phy.en_vc & BIT(i)) { + __csid_configure_rdi_stream(csid, enable, i); +- __csid_configure_rx(csid, &csid->phy, i); + __csid_ctrl_rdi(csid, enable, i); + } + } diff --git a/queue-6.18/media-qcom-camss-fix-rdi-streaming-for-csid-340.patch b/queue-6.18/media-qcom-camss-fix-rdi-streaming-for-csid-340.patch new file mode 100644 index 0000000000..e2c063a3cc --- /dev/null +++ b/queue-6.18/media-qcom-camss-fix-rdi-streaming-for-csid-340.patch @@ -0,0 +1,91 @@ +From stable+bounces-295296-greg=kroah.com@vger.kernel.org Wed Aug 5 02:10:26 2026 +From: Sasha Levin +Date: Tue, 4 Aug 2026 20:08:33 -0400 +Subject: media: qcom: camss: Fix RDI streaming for CSID 340 +To: stable@vger.kernel.org +Cc: Bryan O'Donoghue , Vladimir Zapolskiy , Loic Poulain , Bryan O'Donoghue , Sasha Levin +Message-ID: <20260805000833.3902619-2-sashal@kernel.org> + +From: Bryan O'Donoghue + +[ Upstream commit cc1c35619c9895f918f9e388e3caa6ab2ff2fbe7 ] + +Fix streaming from CSIDn RDI1 and RDI2 to VFEn RDI1 and RDI2. A pattern we +have replicated throughout CAMSS where we use the VC number to populate +both the VC fields and port fields of the CSID means that in practice only +VC = 0 on CSIDn:RDI0 to VFEn:RDI0 works. + +Fix that for CSID 340 by separating VC and port. Fix to VC zero as a bugfix +we will look to properly populate the VC field with follow on patches +later. + +Fixes: f0fc808a466a ("media: qcom: camss: Add CSID 340 support") +Cc: stable@vger.kernel.org +Signed-off-by: Bryan O'Donoghue +Reviewed-by: Vladimir Zapolskiy +Reviewed-by: Loic Poulain +Signed-off-by: Bryan O'Donoghue +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/platform/qcom/camss/camss-csid-340.c | 18 ++++++++++-------- + 1 file changed, 10 insertions(+), 8 deletions(-) + +--- a/drivers/media/platform/qcom/camss/camss-csid-340.c ++++ b/drivers/media/platform/qcom/camss/camss-csid-340.c +@@ -74,9 +74,9 @@ static void __csid_ctrl_rdi(struct csid_ + writel_relaxed(!!enable, csid->base + CSID_RDI_CTRL(rdi)); + } + +-static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 vc) ++static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 port, u8 vc) + { +- struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + vc]; ++ struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + port]; + const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats, + csid->res->formats->nformats, + input_format->code); +@@ -88,14 +88,14 @@ static void __csid_configure_rdi_stream( + * the four least significant bits of the five bit VC + * bitfield to generate an internal CID value. + * +- * CSID_RDI_CFG0(vc) ++ * CSID_RDI_CFG0(port) + * DT_ID : 28:27 + * VC : 26:22 + * DT : 21:16 + * + * CID : VC 3:0 << 2 | DT_ID 1:0 + */ +- dt_id = vc & 0x03; ++ dt_id = port & 0x03; + + val = CSID_RDI_CFG0_DECODE_FORMAT_NOP; /* only for RDI path */ + val |= FIELD_PREP(CSID_RDI_CFG0_DT_MASK, format->data_type); +@@ -105,10 +105,11 @@ static void __csid_configure_rdi_stream( + if (enable) + val |= CSID_RDI_CFG0_ENABLE; + +- dev_dbg(csid->camss->dev, "CSID%u: Stream %s (dt:0x%x vc=%u)\n", +- csid->id, enable ? "enable" : "disable", format->data_type, vc); ++ dev_dbg(csid->camss->dev, "CSID%u: Stream %s (dt:0x%x port=%u vc=%u)\n", ++ csid->id, enable ? "enable" : "disable", format->data_type, ++ port, vc); + +- writel_relaxed(val, csid->base + CSID_RDI_CFG0(vc)); ++ writel_relaxed(val, csid->base + CSID_RDI_CFG0(port)); + } + + static void csid_configure_stream(struct csid_device *csid, u8 enable) +@@ -117,9 +118,10 @@ static void csid_configure_stream(struct + + __csid_configure_rx(csid, &csid->phy); + ++ /* Loop through all enabled ports and configure a stream for each */ + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) { + if (csid->phy.en_vc & BIT(i)) { +- __csid_configure_rdi_stream(csid, enable, i); ++ __csid_configure_rdi_stream(csid, enable, i, 0); + __csid_ctrl_rdi(csid, enable, i); + } + } diff --git a/queue-6.18/media-uapi-rkisp-correct-name-version-enum.patch b/queue-6.18/media-uapi-rkisp-correct-name-version-enum.patch new file mode 100644 index 0000000000..1f662605a4 --- /dev/null +++ b/queue-6.18/media-uapi-rkisp-correct-name-version-enum.patch @@ -0,0 +1,56 @@ +From stable+bounces-296805-greg=kroah.com@vger.kernel.org Thu Aug 6 14:46:50 2026 +From: Sasha Levin +Date: Thu, 6 Aug 2026 08:46:34 -0400 +Subject: media: uapi: rkisp: Correct name version enum +To: stable@vger.kernel.org +Cc: "Niklas Söderlund" , "Laurent Pinchart" , "Hans Verkuil" , "Sasha Levin" +Message-ID: <20260806124634.299943-1-sashal@kernel.org> + +From: Niklas Söderlund + +[ Upstream commit c4c01c4fd4a3916ffdfb35ad9f511c48e289f51c ] + +The name of the enum to hold the mapping of parameter buffer versions +have a typo in the name, correct it. While this is a uAPI header the +impact should be minimal as the enum is only used as a collection for +the one version number supported. + +Fixes: e9d05e9d5db1 ("media: uapi: rkisp1-config: Add extensible params format") +Cc: stable@vger.kernel.org +Signed-off-by: Niklas Söderlund +Reviewed-by: Laurent Pinchart +Link: https://patch.msgid.link/20260501190339.3449193-1-niklas.soderlund+renesas@ragnatech.se +Signed-off-by: Laurent Pinchart +Signed-off-by: Hans Verkuil +[ Adjusted context to keep the tree's `RKISP1_EXT_PARAM_BUFFER_V1 = 1` initializer instead of upstream's `V4L2_ISP_PARAMS_VERSION_V1`, which does not exist in this tree. ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/uapi/linux/rkisp1-config.h | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/include/uapi/linux/rkisp1-config.h ++++ b/include/uapi/linux/rkisp1-config.h +@@ -1583,11 +1583,11 @@ struct rkisp1_ext_params_wdr_config { + sizeof(struct rkisp1_ext_params_wdr_config)) + + /** +- * enum rksip1_ext_param_buffer_version - RkISP1 extensible parameters version ++ * enum rkisp1_ext_param_buffer_version - RkISP1 extensible parameters version + * + * @RKISP1_EXT_PARAM_BUFFER_V1: First version of RkISP1 extensible parameters + */ +-enum rksip1_ext_param_buffer_version { ++enum rkisp1_ext_param_buffer_version { + RKISP1_EXT_PARAM_BUFFER_V1 = 1, + }; + +@@ -1667,7 +1667,7 @@ enum rksip1_ext_param_buffer_version { + * +---------------------------------------------------------------------+ + * + * @version: The RkISP1 extensible parameters buffer version, see +- * :c:type:`rksip1_ext_param_buffer_version` ++ * :c:type:`rkisp1_ext_param_buffer_version` + * @data_size: The RkISP1 configuration data effective size, excluding this + * header + * @data: The RkISP1 extensible configuration data blocks diff --git a/queue-6.18/series b/queue-6.18/series index 57cb4f2302..8ffe76d83c 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -362,3 +362,11 @@ net-handshake-take-a-long-lived-file-reference-at-su.patch net-handshake-hand-off-the-pinned-file-reference-to-.patch net-handshake-close-the-submit-side-sock_hold-race.patch net-handshake-drain-pending-requests-at-net-namespac.patch +usb-typec-ucsi-split-connector-lock-classes.patch +usb-typec-ucsi-fix-race-condition-and-ordering-in-port-unregistration.patch +media-chips-media-wave5-support-cbp-profile.patch +media-qcom-camss-csid-340-fix-unused-variables.patch +media-qcom-camss-fix-rdi-streaming-for-csid-340.patch +media-uapi-rkisp-correct-name-version-enum.patch +wifi-brcmfmac-drain-bus_reset-work-on-device-removal.patch +userfaultfd-prevent-registration-of-special-vmas.patch diff --git a/queue-6.18/usb-typec-ucsi-fix-race-condition-and-ordering-in-port-unregistration.patch b/queue-6.18/usb-typec-ucsi-fix-race-condition-and-ordering-in-port-unregistration.patch new file mode 100644 index 0000000000..97f04b02aa --- /dev/null +++ b/queue-6.18/usb-typec-ucsi-fix-race-condition-and-ordering-in-port-unregistration.patch @@ -0,0 +1,173 @@ +From sashal@kernel.org Thu Jul 30 21:29:55 2026 +From: Sasha Levin +Date: Thu, 30 Jul 2026 15:29:52 -0400 +Subject: usb: typec: ucsi: Fix race condition and ordering in port unregistration +To: stable@vger.kernel.org +Cc: Andrei Kuchynski , stable , Benson Leung , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260730192952.3124595-2-sashal@kernel.org> + +From: Andrei Kuchynski + +[ Upstream commit 7aa7d4bf9d3fa9a6a47b640ad103ab433b7ff261 ] + +A synchronization issue exists during port unregistration where pending +partner work items can race against workqueue destruction, leading to +use-after-free conditions: + + cros_ec_ucsi cros_ec_ucsi.3.auto: error -ETIMEDOUT: PPM init failed + BUG: kernel NULL pointer dereference, address: 0000000000000000 + RIP: 0010:__queue_work+0x83/0x4a0 + Call Trace: + + __cfi_delayed_work_timer_fn+0x10/0x10 + run_timer_softirq+0x3b6/0xbd0 + sched_clock_cpu+0xc/0x110 + irq_exit_rcu+0x18d/0x330 + fred_sysvec_apic_timer_interrupt+0x5e/0x80 + +Fix this by ensuring strict ordering and proper serialization during +teardown: + +1. Move ucsi_unregister_partner() to the beginning of the teardown +sequence and protect it under the connector mutex lock. +2. Ensure all pending partner tasks are explicitly flushed and finished +before the workqueue is destroyed. +3. Switch from mod_delayed_work() to a cancel_delayed_work() and +queue_delayed_work() sequence. This guarantees that items currently marked +as pending won't be scheduled an additional time, preventing a double +release of resources which leads to the following crash: + + Oops: general protection fault, probably for non-canonical address + 0xdead000000000122: 0000 [#1] SMP NOPTI + Workqueue: cros_ec_ucsi.3.auto-con2 ucsi_poll_worker + RIP: 0010:ucsi_poll_worker+0x65/0x1e0 + Call Trace: + + process_scheduled_works+0x218/0x6d0 + worker_thread+0x188/0x3f0 + __cfi_worker_thread+0x10/0x10 + kthread+0x226/0x2a0 + +To ensure these rules are applied identically across both the normal +teardown and the ucsi_init() error paths, consolidate the cleanup logic +into a new helper, ucsi_unregister_port(). + +Cc: stable +Fixes: b9aa02ca39a4 ("usb: typec: ucsi: Add polling mechanism for partner tasks like alt mode checking") +Fixes: b13abcb7ddd8 ("usb: typec: ucsi: Fix NULL pointer access") +Fixes: fac4b8633fd6 ("usb: ucsi: Ensure connector delayed work items are flushed") +Signed-off-by: Andrei Kuchynski +Reviewed-by: Benson Leung +Link: https://patch.msgid.link/20260707141736.1635698-1-akuchynski@chromium.org +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/typec/ucsi/ucsi.c | 82 +++++++++++++++++++----------------------- + 1 file changed, 39 insertions(+), 43 deletions(-) + +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1891,6 +1891,42 @@ out_unlock: + return ret; + } + ++static void ucsi_unregister_port(struct ucsi_connector *con) ++{ ++ struct ucsi_work *uwork; ++ ++ if (con->wq) { ++ mutex_lock(&con->lock); ++ ucsi_unregister_partner(con); ++ /* ++ * queue delayed items immediately so they can execute ++ * and free themselves before the wq is destroyed ++ */ ++ list_for_each_entry(uwork, &con->partner_tasks, node) { ++ if (cancel_delayed_work(&uwork->work)) ++ queue_delayed_work(con->wq, &uwork->work, 0); ++ } ++ mutex_unlock(&con->lock); ++ ++ destroy_workqueue(con->wq); ++ con->wq = NULL; ++ } else { ++ ucsi_unregister_partner(con); ++ } ++ ++ ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); ++ ucsi_unregister_port_psy(con); ++ ++ usb_power_delivery_unregister_capabilities(con->port_sink_caps); ++ con->port_sink_caps = NULL; ++ usb_power_delivery_unregister_capabilities(con->port_source_caps); ++ con->port_source_caps = NULL; ++ usb_power_delivery_unregister(con->pd); ++ con->pd = NULL; ++ typec_unregister_port(con->port); ++ con->port = NULL; ++} ++ + static u64 ucsi_get_supported_notifications(struct ucsi *ucsi) + { + u16 features = ucsi->cap.features; +@@ -2017,22 +2053,8 @@ err_unregister: + for (i = 0; i < ucsi->cap.num_connectors; i++) + lockdep_unregister_key(&connector[i].lock_key); + +- for (con = connector; con->port; con++) { +- if (con->wq) +- destroy_workqueue(con->wq); +- ucsi_unregister_partner(con); +- ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); +- ucsi_unregister_port_psy(con); +- +- usb_power_delivery_unregister_capabilities(con->port_sink_caps); +- con->port_sink_caps = NULL; +- usb_power_delivery_unregister_capabilities(con->port_source_caps); +- con->port_source_caps = NULL; +- usb_power_delivery_unregister(con->pd); +- con->pd = NULL; +- typec_unregister_port(con->port); +- con->port = NULL; +- } ++ for (con = connector; con->port; con++) ++ ucsi_unregister_port(con); + kfree(connector); + err_reset: + memset(&ucsi->cap, 0, sizeof(ucsi->cap)); +@@ -2260,33 +2282,7 @@ void ucsi_unregister(struct ucsi *ucsi) + + for (i = 0; i < ucsi->cap.num_connectors; i++) { + cancel_work_sync(&ucsi->connector[i].work); +- +- if (ucsi->connector[i].wq) { +- struct ucsi_work *uwork; +- +- mutex_lock(&ucsi->connector[i].lock); +- /* +- * queue delayed items immediately so they can execute +- * and free themselves before the wq is destroyed +- */ +- list_for_each_entry(uwork, &ucsi->connector[i].partner_tasks, node) +- mod_delayed_work(ucsi->connector[i].wq, &uwork->work, 0); +- mutex_unlock(&ucsi->connector[i].lock); +- destroy_workqueue(ucsi->connector[i].wq); +- } +- +- ucsi_unregister_partner(&ucsi->connector[i]); +- ucsi_unregister_altmodes(&ucsi->connector[i], +- UCSI_RECIPIENT_CON); +- ucsi_unregister_port_psy(&ucsi->connector[i]); +- +- usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_sink_caps); +- ucsi->connector[i].port_sink_caps = NULL; +- usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_source_caps); +- ucsi->connector[i].port_source_caps = NULL; +- usb_power_delivery_unregister(ucsi->connector[i].pd); +- ucsi->connector[i].pd = NULL; +- typec_unregister_port(ucsi->connector[i].port); ++ ucsi_unregister_port(&ucsi->connector[i]); + lockdep_unregister_key(&ucsi->connector[i].lock_key); + } + diff --git a/queue-6.18/usb-typec-ucsi-split-connector-lock-classes.patch b/queue-6.18/usb-typec-ucsi-split-connector-lock-classes.patch new file mode 100644 index 0000000000..2320b4a1a2 --- /dev/null +++ b/queue-6.18/usb-typec-ucsi-split-connector-lock-classes.patch @@ -0,0 +1,124 @@ +From sashal@kernel.org Thu Jul 30 21:29:55 2026 +From: Sasha Levin +Date: Thu, 30 Jul 2026 15:29:51 -0400 +Subject: usb: typec: ucsi: split connector lock classes +To: stable@vger.kernel.org +Cc: Sergey Senozhatsky , Heikki Krogerus , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260730192952.3124595-1-sashal@kernel.org> + +From: Sergey Senozhatsky + +[ Upstream commit 8c22256bbafad3dc5fdbe9f684d045b67ff06a68 ] + +Lockdep detects a possible recursive locking scenario during +ucsi init: + +[ 5.418616] ============================================ +[ 5.418634] WARNING: possible recursive locking detected +[ 5.418706] -------------------------------------------- +[ 5.418725] kworker/4:1/82 is trying to acquire lock: +[ 5.418759] ffff888119a34648 (&con->lock){+.+.}-{3:3}, at: ucsi_init_work+0x1a78/0x2eb0 [typec_ucsi] +[ 5.418801] + but task is already holding lock: +[ 5.418835] ffff888119a34080 (&con->lock){+.+.}-{3:3}, at: ucsi_init_work+0x1a78/0x2eb0 [typec_ucsi] +[ 5.418884] + other info that might help us debug this: +[ 5.418904] Possible unsafe locking scenario: + +[ 5.418937] CPU0 +[ 5.418956] ---- +[ 5.418991] lock(&con->lock); +[ 5.419013] lock(&con->lock); +[ 5.419033] + *** DEADLOCK *** + +[ 5.419387] Call Trace: +[ 5.419406] +[ 5.419425] dump_stack_lvl+0x61/0xa0 +[ 5.419448] print_deadlock_bug+0x4a6/0x650 +[ 5.419483] __lock_acquire+0x62b6/0x7f50 +[ 5.419507] lock_acquire+0x11b/0x390 +[ 5.419654] __mutex_lock+0xbc/0xcd0 +[ 5.419741] ucsi_init_work+0x1a78/0x2eb0 +[ 5.419785] ? worker_thread+0xf53/0x2bc0 +[ 5.419819] worker_thread+0xff4/0x2bc0 +[ 5.419842] kthread+0x2a7/0x330 +[ 5.419863] ? __pfx_worker_thread+0x10/0x10 +[ 5.419896] ? __pfx_kthread+0x10/0x10 +[ 5.419916] ret_from_fork+0x38/0x70 +[ 5.419936] ? __pfx_kthread+0x10/0x10 +[ 5.419969] ret_from_fork_asm+0x1b/0x30 +[ 5.419991] +[ 5.420009] ---[ end trace 0000000000000000 ]--- + +The problem is that all connector locks belong to the same +lockdep lock class, so the following loop: + + for (i = 0; i < ucsi->cap.num_connectors; i++) + ucsi_register_port(connector[i]) + mutex_lock(&connector[i]->lock) + +looks like a recursive acquire of the same mutex. Put each connector +lock into a dedicated lock class so that lockdep doesn't see it as a +possible recursion. + +Signed-off-by: Sergey Senozhatsky +Reviewed-by: Heikki Krogerus +Link: https://patch.msgid.link/20260515060042.136083-1-senozhatsky@chromium.org +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: 7aa7d4bf9d3f ("usb: typec: ucsi: Fix race condition and ordering in port unregistration") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/typec/ucsi/ucsi.c | 8 ++++++++ + drivers/usb/typec/ucsi/ucsi.h | 1 + + 2 files changed, 9 insertions(+) + +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1741,6 +1741,7 @@ static int ucsi_register_port(struct ucs + INIT_WORK(&con->work, ucsi_handle_connector_change); + init_completion(&con->complete); + mutex_init(&con->lock); ++ lockdep_set_class(&con->lock, &con->lock_key); + INIT_LIST_HEAD(&con->partner_tasks); + con->ucsi = ucsi; + +@@ -1981,6 +1982,9 @@ static int ucsi_init(struct ucsi *ucsi) + goto err_reset; + } + ++ for (i = 0; i < ucsi->cap.num_connectors; i++) ++ lockdep_register_key(&connector[i].lock_key); ++ + /* Register all connectors */ + for (i = 0; i < ucsi->cap.num_connectors; i++) { + connector[i].num = i + 1; +@@ -2010,6 +2014,9 @@ static int ucsi_init(struct ucsi *ucsi) + return 0; + + err_unregister: ++ for (i = 0; i < ucsi->cap.num_connectors; i++) ++ lockdep_unregister_key(&connector[i].lock_key); ++ + for (con = connector; con->port; con++) { + if (con->wq) + destroy_workqueue(con->wq); +@@ -2280,6 +2287,7 @@ void ucsi_unregister(struct ucsi *ucsi) + usb_power_delivery_unregister(ucsi->connector[i].pd); + ucsi->connector[i].pd = NULL; + typec_unregister_port(ucsi->connector[i].port); ++ lockdep_unregister_key(&ucsi->connector[i].lock_key); + } + + kfree(ucsi->connector); +--- a/drivers/usb/typec/ucsi/ucsi.h ++++ b/drivers/usb/typec/ucsi/ucsi.h +@@ -502,6 +502,7 @@ struct ucsi_connector { + + struct ucsi *ucsi; + struct mutex lock; /* port lock */ ++ struct lock_class_key lock_key; + struct work_struct work; + struct completion complete; + struct workqueue_struct *wq; diff --git a/queue-6.18/userfaultfd-prevent-registration-of-special-vmas.patch b/queue-6.18/userfaultfd-prevent-registration-of-special-vmas.patch new file mode 100644 index 0000000000..8267933517 --- /dev/null +++ b/queue-6.18/userfaultfd-prevent-registration-of-special-vmas.patch @@ -0,0 +1,62 @@ +From stable+bounces-297128-greg=kroah.com@vger.kernel.org Fri Aug 7 05:36:00 2026 +From: Sasha Levin +Date: Thu, 6 Aug 2026 23:34:59 -0400 +Subject: userfaultfd: prevent registration of special VMAs +To: stable@vger.kernel.org +Cc: "Mike Rapoport (Microsoft)" , vova tokarev , "David Hildenbrand (Arm)" , Lorenzo Stoakes , Al Viro , Christian Brauner , Jan Kara , Linus Torvalds , Oleg Nesterov , Peter Xu , Andrew Morton , Sasha Levin +Message-ID: <20260807033459.1725973-1-sashal@kernel.org> + +From: "Mike Rapoport (Microsoft)" + +[ Upstream commit 3c58f641e813c3c71039f8fd4d4e2a3aab713288 ] + +Vova Tokarev says: + + userfaultfd allows registration on shadow stack VMAs. With userfaultfd + access, you can register on the shadow stack, discard a page ... and + inject a page with chosen return addresses via UFFDIO_COPY. + +Update vma_can_userfault() to reject VM_SHADOW_STACK. + +While on it, also reject VM_SPECIAL so that if a driver would implement +vm_uffd_ops, it wouldn't be possible to register special VMAs with +userfaultfd. + +Since VM_SPECIAL includes VM_DONTEXPAND which is set but hugetlb, exclude +hugetlb VMAs from the check for VM_SPECIAL. + +Link: https://lore.kernel.org/20260618095017.2553004-1-rppt@kernel.org +Fixes: 54007f818206 ("mm: Introduce VM_SHADOW_STACK for shadow stack memory") +Signed-off-by: Mike Rapoport (Microsoft) +Reported-by: vova tokarev +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Lorenzo Stoakes +Cc: Al Viro +Cc: Christian Brauner +Cc: Jan Kara +Cc: Linus Torvalds +Cc: Mike Rapoport +Cc: Oleg Nesterov +Cc: Peter Xu +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/userfaultfd_k.h | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +--- a/include/linux/userfaultfd_k.h ++++ b/include/linux/userfaultfd_k.h +@@ -214,7 +214,10 @@ static inline bool vma_can_userfault(str + { + vm_flags &= __VM_UFFD_FLAGS; + +- if (vma->vm_flags & VM_DROPPABLE) ++ if (vma->vm_flags & (VM_DROPPABLE | VM_SHADOW_STACK)) ++ return false; ++ ++ if (!is_vm_hugetlb_page(vma) && (vma->vm_flags & VM_SPECIAL)) + return false; + + if ((vm_flags & VM_UFFD_MINOR) && diff --git a/queue-6.18/wifi-brcmfmac-drain-bus_reset-work-on-device-removal.patch b/queue-6.18/wifi-brcmfmac-drain-bus_reset-work-on-device-removal.patch new file mode 100644 index 0000000000..af217ad618 --- /dev/null +++ b/queue-6.18/wifi-brcmfmac-drain-bus_reset-work-on-device-removal.patch @@ -0,0 +1,292 @@ +From stable+bounces-296817-greg=kroah.com@vger.kernel.org Thu Aug 6 15:18:34 2026 +From: Sasha Levin +Date: Thu, 6 Aug 2026 09:17:40 -0400 +Subject: wifi: brcmfmac: drain bus_reset work on device removal +To: stable@vger.kernel.org +Cc: Fan Wu , Arend van Spriel , Johannes Berg , Sasha Levin +Message-ID: <20260806131740.402812-1-sashal@kernel.org> + +From: Fan Wu + +[ Upstream commit 43b25879f004c98defa2776bedc6ca4763c51945 ] + +brcmf_fw_crashed() and the debugfs "reset" entry both schedule +drvr->bus_reset, whose callback recovers drvr through container_of() +and dereferences it. The removal path frees drvr (brcmf_free -> +wiphy_free) without draining the work, so a bus_reset callback pending +or running during removal can outlive drvr. + +Cancellation cannot live in brcmf_detach() or brcmf_free(): the work +callback reaches teardown through the bus .reset op (PCIe +brcmf_pcie_reset -> brcmf_detach; SDIO brcmf_sdio_bus_reset -> +brcmf_sdiod_remove -> brcmf_free), so cancelling there would wait for +the running work and deadlock. + +Add a per-bus mutex (bus_reset_lock) and route all arming through +brcmf_bus_schedule_reset(), which under the lock skips when the bus is +marked removing. Each bus remove entry calls +brcmf_bus_cancel_reset_work(), which under the same lock sets removing +and cancels the work. Holding the mutex across cancel_work_sync() makes +the set-removing + drain step atomic. Every producer reaches the arming +path from process context -- the PCIe firmware-halt notification runs in +the threaded IRQ handler (brcmf_pcie_isr_thread) and the SDIO hostmail +path runs from the data workqueue -- so the mutex is taken only in +sleepable contexts. Where applicable the remove entry first stops the +firmware-crash producer: on PCIe mask the mailbox and synchronize_irq; +on SDIO unregister the bus interrupt and cancel the data worker, which +also reports firmware halts through brcmf_fw_crashed(). The mutex is +initialized at bus allocation. The SDIO suspend power-off path frees +drvr through the same brcmf_sdiod_remove() and takes the same lock; +resume re-allows the work only on a successful re-probe. + +Also guard brcmf_fw_crashed() against a NULL bus_if/drvr: it can fire +before brcmf_attach() wires up drvr, and it dereferences drvr +(bphy_err/brcmf_dev_coredump) before reaching the arming gate. + +The bus_reset work is shared across buses, so the drain is applied to +every remove path: PCIe (the .reset op introduced by the Fixes commit), +SDIO (arms the same work through brcmf_fw_crashed()), and USB (via the +debugfs "reset" entry). cancel_work_sync() drains a running or pending +bus_reset work item before removal frees drvr, and patch 1/2 makes the +scratch-buffer release safe when reset teardown has already released +those DMA buffers. + +This patch fixes the lifetime of the bus_reset work item itself. It does +not attempt to address the separate, pre-existing lifetime of the +asynchronous firmware completion started by the PCIe reset path. That +callback needs its own lifetime/ownership protocol and is being tracked +separately. + +This issue was found by an in-house static analysis tool. + +Fixes: 4684997d9eea ("brcmfmac: reset PCIe bus on a firmware crash") +Cc: stable@vger.kernel.org +Signed-off-by: Fan Wu +Assisted-by: Codex:gpt-5.6 +Acked-by: Arend van Spriel +Link: https://patch.msgid.link/20260718024353.3147201-3-fanwu01@zju.edu.cn +Signed-off-by: Johannes Berg +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c | 13 +++ + drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h | 6 + + drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c | 46 ++++++++++++-- + drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 6 + + drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c | 6 + + drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h | 1 + drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c | 3 + 7 files changed, 77 insertions(+), 4 deletions(-) + +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c +@@ -1070,6 +1070,7 @@ static int brcmf_ops_sdio_probe(struct s + bus_if = kzalloc(sizeof(*bus_if), GFP_KERNEL); + if (!bus_if) + return -ENOMEM; ++ mutex_init(&bus_if->bus_reset_lock); + sdiodev = kzalloc(sizeof(*sdiodev), GFP_KERNEL); + if (!sdiodev) { + kfree(bus_if); +@@ -1131,6 +1132,14 @@ static void brcmf_ops_sdio_remove(struct + if (func->num != 1) + return; + ++ /* Drain bus_reset before the shared brcmf_sdiod_remove() ++ * teardown, which the SDIO reset callback also reaches. The ++ * data worker can arm bus_reset via brcmf_fw_crashed(); cancel ++ * it first. ++ */ ++ brcmf_sdio_cancel_datawork(sdiodev->bus); ++ brcmf_bus_cancel_reset_work(bus_if); ++ + /* only proceed with rest of cleanup if func 1 */ + brcmf_sdiod_remove(sdiodev); + +@@ -1205,6 +1214,8 @@ static int brcmf_ops_sdio_suspend(struct + } else { + /* power will be cut so remove device, probe again in resume */ + brcmf_sdiod_intr_unregister(sdiodev); ++ brcmf_sdio_cancel_datawork(sdiodev->bus); ++ brcmf_bus_cancel_reset_work(bus_if); + ret = brcmf_sdiod_remove(sdiodev); + if (ret) + brcmf_err("Failed to remove device on suspend\n"); +@@ -1230,6 +1241,8 @@ static int brcmf_ops_sdio_resume(struct + ret = brcmf_sdiod_probe(sdiodev); + if (ret) + brcmf_err("Failed to probe device on resume\n"); ++ else ++ brcmf_bus_allow_reset_work(bus_if); + } else { + if (sdiodev->wowl_enabled && sdiodev->settings->bus.sdio.oob_irq_supported) + disable_irq_wake(sdiodev->settings->bus.sdio.oob_irq_nr); +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include "debug.h" + + /* IDs of the 6 default common rings of msgbuf protocol */ +@@ -179,6 +180,8 @@ struct brcmf_bus { + enum brcmf_fwvendor fwvid; + bool always_use_fws_queue; + bool wowl_supported; ++ bool removing; /* device removal in progress; quiesce async work */ ++ struct mutex bus_reset_lock; + + const struct brcmf_bus_ops *ops; + struct brcmf_bus_msgbuf *msgbuf; +@@ -186,6 +189,9 @@ struct brcmf_bus { + struct list_head list; + }; + ++void brcmf_bus_cancel_reset_work(struct brcmf_bus *bus_if); ++void brcmf_bus_allow_reset_work(struct brcmf_bus *bus_if); ++ + /* + * callback wrappers + */ +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +@@ -1167,6 +1167,35 @@ static int brcmf_revinfo_read(struct seq + return 0; + } + ++/* ++ * Serialize arming from debugfs reset and brcmf_fw_crashed() against ++ * teardown. The remove path sets ->removing and drains the work while ++ * holding bus_reset_lock, so a racing armer is either drained or skips it. ++ */ ++static void brcmf_bus_schedule_reset(struct brcmf_bus *bus_if) ++{ ++ mutex_lock(&bus_if->bus_reset_lock); ++ if (bus_if->drvr && bus_if->drvr->bus_reset.func && !bus_if->removing) ++ schedule_work(&bus_if->drvr->bus_reset); ++ mutex_unlock(&bus_if->bus_reset_lock); ++} ++ ++void brcmf_bus_cancel_reset_work(struct brcmf_bus *bus_if) ++{ ++ mutex_lock(&bus_if->bus_reset_lock); ++ bus_if->removing = true; ++ if (bus_if->drvr) ++ cancel_work_sync(&bus_if->drvr->bus_reset); ++ mutex_unlock(&bus_if->bus_reset_lock); ++} ++ ++void brcmf_bus_allow_reset_work(struct brcmf_bus *bus_if) ++{ ++ mutex_lock(&bus_if->bus_reset_lock); ++ bus_if->removing = false; ++ mutex_unlock(&bus_if->bus_reset_lock); ++} ++ + static void brcmf_core_bus_reset(struct work_struct *work) + { + struct brcmf_pub *drvr = container_of(work, struct brcmf_pub, +@@ -1187,7 +1216,7 @@ static ssize_t bus_reset_write(struct fi + if (value != 1) + return -EINVAL; + +- schedule_work(&drvr->bus_reset); ++ brcmf_bus_schedule_reset(drvr->bus_if); + + return count; + } +@@ -1417,14 +1446,23 @@ void brcmf_dev_coredump(struct device *d + void brcmf_fw_crashed(struct device *dev) + { + struct brcmf_bus *bus_if = dev_get_drvdata(dev); +- struct brcmf_pub *drvr = bus_if->drvr; ++ struct brcmf_pub *drvr; ++ ++ /* May fire before brcmf_attach() wires up drvr, or after removal ++ * has cleared it; guard the derefs below (and the arming gate in ++ * brcmf_bus_schedule_reset() already checks drvr/->removing). ++ */ ++ if (!bus_if) ++ return; ++ drvr = bus_if->drvr; ++ if (!drvr) ++ return; + + bphy_err(drvr, "Firmware has halted or crashed\n"); + + brcmf_dev_coredump(dev); + +- if (drvr->bus_reset.func) +- schedule_work(&drvr->bus_reset); ++ brcmf_bus_schedule_reset(bus_if); + } + + void brcmf_detach(struct device *dev) +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +@@ -2504,6 +2504,7 @@ brcmf_pcie_probe(struct pci_dev *pdev, c + ret = -ENOMEM; + goto fail; + } ++ mutex_init(&bus->bus_reset_lock); + bus->msgbuf = kzalloc(sizeof(*bus->msgbuf), GFP_KERNEL); + if (!bus->msgbuf) { + ret = -ENOMEM; +@@ -2599,6 +2600,11 @@ brcmf_pcie_remove(struct pci_dev *pdev) + if (devinfo->ci) + brcmf_pcie_intr_disable(devinfo); + ++ if (devinfo->irq_allocated) ++ synchronize_irq(pdev->irq); ++ ++ brcmf_bus_cancel_reset_work(bus); ++ + brcmf_detach(&pdev->dev); + brcmf_free(&pdev->dev); + +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +@@ -4560,6 +4560,12 @@ fail: + return ret; + } + ++void brcmf_sdio_cancel_datawork(struct brcmf_sdio *bus) ++{ ++ if (bus) ++ cancel_work_sync(&bus->datawork); ++} ++ + /* Detach and free everything */ + void brcmf_sdio_remove(struct brcmf_sdio *bus) + { +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h +@@ -361,6 +361,7 @@ int brcmf_sdiod_remove(struct brcmf_sdio + int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev); + void brcmf_sdio_remove(struct brcmf_sdio *bus); + void brcmf_sdio_isr(struct brcmf_sdio *bus, bool in_isr); ++void brcmf_sdio_cancel_datawork(struct brcmf_sdio *bus); + + void brcmf_sdio_wd_timer(struct brcmf_sdio *bus, bool active); + void brcmf_sdio_wowl_config(struct device *dev, bool enabled); +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c +@@ -1260,6 +1260,7 @@ static int brcmf_usb_probe_cb(struct brc + ret = -ENOMEM; + goto fail; + } ++ mutex_init(&bus->bus_reset_lock); + + bus->dev = dev; + bus_pub->bus = bus; +@@ -1329,6 +1330,8 @@ brcmf_usb_disconnect_cb(struct brcmf_usb + return; + brcmf_dbg(USB, "Enter, bus_pub %p\n", devinfo); + ++ brcmf_bus_cancel_reset_work(devinfo->bus_pub.bus); ++ + brcmf_detach(devinfo->dev); + brcmf_free(devinfo->dev); + kfree(devinfo->bus_pub.bus);