summaryrefslogtreecommitdiff
path: root/drivers/input/misc/ims-pcu.c
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-07-24 16:17:26 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-07-24 16:17:26 +0200
commitb62ed4c93ad71374b54d2c3a72cbc67b506f580c (patch)
tree6ca6ab974bbce902fc9de300fea6aa056170850f /drivers/input/misc/ims-pcu.c
parent5895db67c12464003afd16c08049b73aa09e58ea (diff)
parent221fc2f4d0eda59d02af2e751a9282fa013a8e97 (diff)
Merge v6.18.40linux-rolling-lts
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/input/misc/ims-pcu.c')
-rw-r--r--drivers/input/misc/ims-pcu.c129
1 files changed, 112 insertions, 17 deletions
diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c
index 22d34644254d..94113e14bf59 100644
--- a/drivers/input/misc/ims-pcu.c
+++ b/drivers/input/misc/ims-pcu.c
@@ -14,6 +14,7 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/slab.h>
+#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/usb/input.h>
#include <linux/usb/cdc.h>
@@ -406,7 +407,16 @@ static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
static void ims_pcu_report_events(struct ims_pcu *pcu)
{
- u32 data = get_unaligned_be32(&pcu->read_buf[3]);
+ u32 data;
+
+ /* 6-axis setting (1 byte) + button data + checksum */
+ if (pcu->read_pos < IMS_PCU_DATA_OFFSET + 1 + sizeof(data) + 1) {
+ dev_warn(pcu->dev, "Short buttons report: %d bytes\n",
+ pcu->read_pos);
+ return;
+ }
+
+ data = get_unaligned_be32(&pcu->read_buf[IMS_PCU_DATA_OFFSET + 1]);
ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK);
if (pcu->gamepad)
@@ -666,11 +676,19 @@ static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu,
return error;
}
- if (expected_response && pcu->cmd_buf[2] != expected_response) {
- dev_err(pcu->dev,
- "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n",
- pcu->cmd_buf[2], expected_response);
- return -EINVAL;
+ if (expected_response) {
+ if (pcu->cmd_buf_len < 3) {
+ dev_err(pcu->dev, "Short response from bootloader: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
+ if (pcu->cmd_buf[2] != expected_response) {
+ dev_err(pcu->dev,
+ "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n",
+ pcu->cmd_buf[2], expected_response);
+ return -EINVAL;
+ }
}
return 0;
@@ -698,6 +716,12 @@ static int ims_pcu_get_info(struct ims_pcu *pcu)
return error;
}
+ if (pcu->cmd_buf_len < IMS_PCU_DATA_OFFSET + IMS_PCU_SET_INFO_SIZE + 1) {
+ dev_err(pcu->dev, "Short GET_INFO response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
memcpy(pcu->part_number,
&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET],
sizeof(pcu->part_number));
@@ -795,6 +819,12 @@ static int ims_pcu_verify_block(struct ims_pcu *pcu,
return error;
}
+ if (pcu->cmd_buf_len < IMS_PCU_BL_DATA_OFFSET + sizeof(*fragment) + len + 1) {
+ dev_err(pcu->dev, "Short READ_APP response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET];
if (get_unaligned_le32(&fragment->addr) != addr ||
fragment->len != len) {
@@ -914,9 +944,10 @@ out:
return retval;
}
-static void ims_pcu_process_async_firmware(const struct firmware *fw,
+static void ims_pcu_process_async_firmware(const struct firmware *_fw,
void *context)
{
+ const struct firmware *fw __free(firmware) = _fw;
struct ims_pcu *pcu = context;
int error;
@@ -936,8 +967,6 @@ static void ims_pcu_process_async_firmware(const struct firmware *fw,
scoped_guard(mutex, &pcu->cmd_mutex)
ims_pcu_handle_firmware_update(pcu, fw);
- release_firmware(fw);
-
out:
complete(&pcu->async_firmware_done);
}
@@ -989,6 +1018,10 @@ ims_pcu_backlight_get_brightness(struct led_classdev *cdev)
error);
/* Assume the LED is OFF */
brightness = LED_OFF;
+ } else if (pcu->cmd_buf_len < IMS_PCU_DATA_OFFSET + 2 + 1) {
+ dev_err(pcu->dev, "Short GET_BRIGHTNESS response: %d bytes\n",
+ pcu->cmd_buf_len);
+ brightness = LED_OFF;
} else {
brightness =
get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]);
@@ -1133,6 +1166,8 @@ static ssize_t ims_pcu_reset_device(struct device *dev,
dev_info(pcu->dev, "Attempting to reset device\n");
+ guard(mutex)(&pcu->cmd_mutex);
+
error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1);
if (error) {
dev_info(pcu->dev,
@@ -1229,6 +1264,9 @@ static umode_t ims_pcu_is_attr_visible(struct kobject *kobj,
struct ims_pcu *pcu = usb_get_intfdata(intf);
umode_t mode = attr->mode;
+ if (intf != pcu->ctrl_intf)
+ return 0;
+
if (pcu->bootloader_mode) {
if (attr != &dev_attr_update_firmware_status.attr &&
attr != &dev_attr_update_firmware.attr &&
@@ -1262,6 +1300,12 @@ static int ims_pcu_read_ofn_config(struct ims_pcu *pcu, u8 addr, u8 *data)
if (error)
return error;
+ if (pcu->cmd_buf_len < OFN_REG_RESULT_OFFSET + 2 + 1) {
+ dev_err(pcu->dev, "Short OFN_GET_CONFIG response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
if (result < 0)
return -EIO;
@@ -1282,6 +1326,12 @@ static int ims_pcu_write_ofn_config(struct ims_pcu *pcu, u8 addr, u8 data)
if (error)
return error;
+ if (pcu->cmd_buf_len < OFN_REG_RESULT_OFFSET + 2 + 1) {
+ dev_err(pcu->dev, "Short OFN_SET_CONFIG response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
if (result < 0)
return -EIO;
@@ -1468,6 +1518,9 @@ static umode_t ims_pcu_ofn_is_attr_visible(struct kobject *kobj,
struct ims_pcu *pcu = usb_get_intfdata(intf);
umode_t mode = attr->mode;
+ if (intf != pcu->ctrl_intf)
+ return SYSFS_GROUP_INVISIBLE;
+
/*
* PCU-B devices, both GEN_1 and GEN_2 do not have OFN sensor.
*/
@@ -1508,7 +1561,7 @@ static void ims_pcu_irq(struct urb *urb)
}
dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__,
- urb->actual_length, urb->actual_length, pcu->urb_in_buf);
+ urb->actual_length, urb->actual_length, urb->transfer_buffer);
if (urb == pcu->urb_in)
ims_pcu_process_data(pcu, urb);
@@ -1636,8 +1689,9 @@ ims_pcu_get_cdc_union_desc(struct usb_interface *intf)
while (buflen >= sizeof(*union_desc)) {
union_desc = (struct usb_cdc_union_desc *)buf;
- if (union_desc->bLength > buflen) {
- dev_err(&intf->dev, "Too large descriptor\n");
+ if (union_desc->bLength < 2 || union_desc->bLength > buflen) {
+ dev_err(&intf->dev, "Invalid descriptor length: %d\n",
+ union_desc->bLength);
return NULL;
}
@@ -1673,7 +1727,7 @@ static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pc
pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev,
union_desc->bMasterInterface0);
- if (!pcu->ctrl_intf)
+ if (pcu->ctrl_intf != intf)
return -EINVAL;
alt = pcu->ctrl_intf->cur_altsetting;
@@ -1682,6 +1736,12 @@ static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pc
return -ENODEV;
pcu->ep_ctrl = &alt->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(pcu->ep_ctrl)) {
+ dev_err(pcu->dev,
+ "Control endpoint is not INTERRUPT IN\n");
+ return -EINVAL;
+ }
+
pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl);
pcu->data_intf = usb_ifnum_to_if(pcu->udev,
@@ -1763,11 +1823,16 @@ static void ims_pcu_stop_io(struct ims_pcu *pcu)
static int ims_pcu_line_setup(struct ims_pcu *pcu)
{
struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting;
- struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf;
+ struct usb_cdc_line_coding *line __free(kfree) =
+ kmalloc(sizeof(*line), GFP_KERNEL);
int error;
- memset(line, 0, sizeof(*line));
+ if (!line)
+ return -ENOMEM;
+
line->dwDTERate = cpu_to_le32(57600);
+ line->bCharFormat = USB_CDC_1_STOP_BITS;
+ line->bParityType = USB_CDC_NO_PARITY;
line->bDataBits = 8;
error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0),
@@ -1811,6 +1876,12 @@ static int ims_pcu_get_device_info(struct ims_pcu *pcu)
return error;
}
+ if (pcu->cmd_buf_len < IMS_PCU_DATA_OFFSET + 6 + 1) {
+ dev_err(pcu->dev, "Short GET_FW_VERSION response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
snprintf(pcu->fw_version, sizeof(pcu->fw_version),
"%02d%02d%02d%02d.%c%c",
pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5],
@@ -1823,6 +1894,12 @@ static int ims_pcu_get_device_info(struct ims_pcu *pcu)
return error;
}
+ if (pcu->cmd_buf_len < IMS_PCU_DATA_OFFSET + 6 + 1) {
+ dev_err(pcu->dev, "Short GET_BL_VERSION response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
snprintf(pcu->bl_version, sizeof(pcu->bl_version),
"%02d%02d%02d%02d.%c%c",
pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5],
@@ -1835,6 +1912,12 @@ static int ims_pcu_get_device_info(struct ims_pcu *pcu)
return error;
}
+ if (pcu->cmd_buf_len < IMS_PCU_DATA_OFFSET + 1 + 1) {
+ dev_err(pcu->dev, "Short RESET_REASON response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
snprintf(pcu->reset_reason, sizeof(pcu->reset_reason),
"%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]);
@@ -1861,6 +1944,12 @@ static int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id)
return error;
}
+ if (pcu->cmd_buf_len < IMS_PCU_DATA_OFFSET + 1 + 1) {
+ dev_err(pcu->dev, "Short GET_DEVICE_ID response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
*device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET];
dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id);
@@ -1952,6 +2041,12 @@ static int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu)
return error;
}
+ if (pcu->cmd_buf_len < IMS_PCU_DATA_OFFSET + 15 + 4 + 1) {
+ dev_err(pcu->dev, "Short QUERY_DEVICE response: %d bytes\n",
+ pcu->cmd_buf_len);
+ return -EIO;
+ }
+
pcu->fw_start_addr =
get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]);
pcu->fw_end_addr =
@@ -2051,7 +2146,6 @@ err_free_mem:
static void ims_pcu_disconnect(struct usb_interface *intf)
{
struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct usb_host_interface *alt = intf->cur_altsetting;
usb_set_intfdata(intf, NULL);
@@ -2059,7 +2153,7 @@ static void ims_pcu_disconnect(struct usb_interface *intf)
* See if we are dealing with control or data interface. The cleanup
* happens when we unbind primary (control) interface.
*/
- if (alt->desc.bInterfaceClass != USB_CLASS_COMM)
+ if (intf != pcu->ctrl_intf)
return;
ims_pcu_stop_io(pcu);
@@ -2070,6 +2164,7 @@ static void ims_pcu_disconnect(struct usb_interface *intf)
ims_pcu_destroy_application_mode(pcu);
ims_pcu_buffers_free(pcu);
+ usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf);
kfree(pcu);
}