cregit-Linux how code gets into the kernel

Release 4.11 drivers/input/misc/ims-pcu.c

/*
 * Driver for IMS Passenger Control Unit Devices
 *
 * Copyright (C) 2013 The IMS Company
 *
 * 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 <linux/completion.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/ihex.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/usb/input.h>
#include <linux/usb/cdc.h>
#include <asm/unaligned.h>


#define IMS_PCU_KEYMAP_LEN		32


struct ims_pcu_buttons {
	
struct input_dev *input;
	
char name[32];
	
char phys[32];
	
unsigned short keymap[IMS_PCU_KEYMAP_LEN];
};


struct ims_pcu_gamepad {
	
struct input_dev *input;
	
char name[32];
	
char phys[32];
};


struct ims_pcu_backlight {
	
struct led_classdev cdev;
	
struct work_struct work;
	
enum led_brightness desired_brightness;
	
char name[32];
};


#define IMS_PCU_PART_NUMBER_LEN		15

#define IMS_PCU_SERIAL_NUMBER_LEN	8

#define IMS_PCU_DOM_LEN			8

#define IMS_PCU_FW_VERSION_LEN		(9 + 1)

#define IMS_PCU_BL_VERSION_LEN		(9 + 1)

#define IMS_PCU_BL_RESET_REASON_LEN	(2 + 1)


#define IMS_PCU_PCU_B_DEVICE_ID		5


#define IMS_PCU_BUF_SIZE		128


struct ims_pcu {
	
struct usb_device *udev;
	
struct device *dev; /* control interface's device, used for logging */

	
unsigned int device_no;

	
bool bootloader_mode;

	
char part_number[IMS_PCU_PART_NUMBER_LEN];
	
char serial_number[IMS_PCU_SERIAL_NUMBER_LEN];
	
char date_of_manufacturing[IMS_PCU_DOM_LEN];
	
char fw_version[IMS_PCU_FW_VERSION_LEN];
	
char bl_version[IMS_PCU_BL_VERSION_LEN];
	
char reset_reason[IMS_PCU_BL_RESET_REASON_LEN];
	
int update_firmware_status;
	
u8 device_id;

	
u8 ofn_reg_addr;

	
struct usb_interface *ctrl_intf;

	
struct usb_endpoint_descriptor *ep_ctrl;
	
struct urb *urb_ctrl;
	
u8 *urb_ctrl_buf;
	
dma_addr_t ctrl_dma;
	
size_t max_ctrl_size;

	
struct usb_interface *data_intf;

	
struct usb_endpoint_descriptor *ep_in;
	
struct urb *urb_in;
	
u8 *urb_in_buf;
	
dma_addr_t read_dma;
	
size_t max_in_size;

	
struct usb_endpoint_descriptor *ep_out;
	
u8 *urb_out_buf;
	
size_t max_out_size;

	
u8 read_buf[IMS_PCU_BUF_SIZE];
	
u8 read_pos;
	
u8 check_sum;
	
bool have_stx;
	
bool have_dle;

	
u8 cmd_buf[IMS_PCU_BUF_SIZE];
	
u8 ack_id;
	
u8 expected_response;
	
u8 cmd_buf_len;
	
struct completion cmd_done;
	
struct mutex cmd_mutex;

	
u32 fw_start_addr;
	
u32 fw_end_addr;
	
struct completion async_firmware_done;

	
struct ims_pcu_buttons buttons;
	
struct ims_pcu_gamepad *gamepad;
	
struct ims_pcu_backlight backlight;

	
bool setup_complete; /* Input and LED devices have been created */
};


/*********************************************************************
 *             Buttons Input device support                          *
 *********************************************************************/


static const unsigned short ims_pcu_keymap_1[] = {
	[1] = KEY_ATTENDANT_OFF,
	[2] = KEY_ATTENDANT_ON,
	[3] = KEY_LIGHTS_TOGGLE,
	[4] = KEY_VOLUMEUP,
	[5] = KEY_VOLUMEDOWN,
	[6] = KEY_INFO,
};


static const unsigned short ims_pcu_keymap_2[] = {
	[4] = KEY_VOLUMEUP,
	[5] = KEY_VOLUMEDOWN,
	[6] = KEY_INFO,
};


static const unsigned short ims_pcu_keymap_3[] = {
	[1] = KEY_HOMEPAGE,
	[2] = KEY_ATTENDANT_TOGGLE,
	[3] = KEY_LIGHTS_TOGGLE,
	[4] = KEY_VOLUMEUP,
	[5] = KEY_VOLUMEDOWN,
	[6] = KEY_DISPLAYTOGGLE,
	[18] = KEY_PLAYPAUSE,
};


static const unsigned short ims_pcu_keymap_4[] = {
	[1] = KEY_ATTENDANT_OFF,
	[2] = KEY_ATTENDANT_ON,
	[3] = KEY_LIGHTS_TOGGLE,
	[4] = KEY_VOLUMEUP,
	[5] = KEY_VOLUMEDOWN,
	[6] = KEY_INFO,
	[18] = KEY_PLAYPAUSE,
};


static const unsigned short ims_pcu_keymap_5[] = {
	[1] = KEY_ATTENDANT_OFF,
	[2] = KEY_ATTENDANT_ON,
	[3] = KEY_LIGHTS_TOGGLE,
};


struct ims_pcu_device_info {
	
const unsigned short *keymap;
	
size_t keymap_len;
	
bool has_gamepad;
};


#define IMS_PCU_DEVINFO(_n, _gamepad)				\
	[_n] = {                                                \
                .keymap = ims_pcu_keymap_##_n,                  \
                .keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n),  \
                .has_gamepad = _gamepad,                        \
        }


static const struct ims_pcu_device_info ims_pcu_device_info[] = {
	IMS_PCU_DEVINFO(1, true),
	IMS_PCU_DEVINFO(2, true),
	IMS_PCU_DEVINFO(3, true),
	IMS_PCU_DEVINFO(4, true),
	IMS_PCU_DEVINFO(5, false),
};


static void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data) { struct ims_pcu_buttons *buttons = &pcu->buttons; struct input_dev *input = buttons->input; int i; for (i = 0; i < 32; i++) { unsigned short keycode = buttons->keymap[i]; if (keycode != KEY_RESERVED) input_report_key(input, keycode, data & (1UL << i)); } input_sync(input); }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov88100.00%1100.00%
Total88100.00%1100.00%


static int ims_pcu_setup_buttons(struct ims_pcu *pcu, const unsigned short *keymap, size_t keymap_len) { struct ims_pcu_buttons *buttons = &pcu->buttons; struct input_dev *input; int i; int error; input = input_allocate_device(); if (!input) { dev_err(pcu->dev, "Not enough memory for input input device\n"); return -ENOMEM; } snprintf(buttons->name, sizeof(buttons->name), "IMS PCU#%d Button Interface", pcu->device_no); usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys)); strlcat(buttons->phys, "/input0", sizeof(buttons->phys)); memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len); input->name = buttons->name; input->phys = buttons->phys; usb_to_input_id(pcu->udev, &input->id); input->dev.parent = &pcu->ctrl_intf->dev; input->keycode = buttons->keymap; input->keycodemax = ARRAY_SIZE(buttons->keymap); input->keycodesize = sizeof(buttons->keymap[0]); __set_bit(EV_KEY, input->evbit); for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++) __set_bit(buttons->keymap[i], input->keybit); __clear_bit(KEY_RESERVED, input->keybit); error = input_register_device(input); if (error) { dev_err(pcu->dev, "Failed to register buttons input device: %d\n", error); input_free_device(input); return error; } buttons->input = input; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov297100.00%1100.00%
Total297100.00%1100.00%


static void ims_pcu_destroy_buttons(struct ims_pcu *pcu) { struct ims_pcu_buttons *buttons = &pcu->buttons; input_unregister_device(buttons->input); }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov28100.00%1100.00%
Total28100.00%1100.00%

/********************************************************************* * Gamepad Input device support * *********************************************************************/
static void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data) { struct ims_pcu_gamepad *gamepad = pcu->gamepad; struct input_dev *input = gamepad->input; int x, y; x = !!(data & (1 << 14)) - !!(data & (1 << 13)); y = !!(data & (1 << 12)) - !!(data & (1 << 11)); input_report_abs(input, ABS_X, x); input_report_abs(input, ABS_Y, y); input_report_key(input, BTN_A, data & (1 << 7)); input_report_key(input, BTN_B, data & (1 << 8)); input_report_key(input, BTN_X, data & (1 << 9)); input_report_key(input, BTN_Y, data & (1 << 10)); input_report_key(input, BTN_START, data & (1 << 15)); input_report_key(input, BTN_SELECT, data & (1 << 16)); input_sync(input); }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov202100.00%1100.00%
Total202100.00%1100.00%


static int ims_pcu_setup_gamepad(struct ims_pcu *pcu) { struct ims_pcu_gamepad *gamepad; struct input_dev *input; int error; gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL); input = input_allocate_device(); if (!gamepad || !input) { dev_err(pcu->dev, "Not enough memory for gamepad device\n"); error = -ENOMEM; goto err_free_mem; } gamepad->input = input; snprintf(gamepad->name, sizeof(gamepad->name), "IMS PCU#%d Gamepad Interface", pcu->device_no); usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys)); strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys)); input->name = gamepad->name; input->phys = gamepad->phys; usb_to_input_id(pcu->udev, &input->id); input->dev.parent = &pcu->ctrl_intf->dev; __set_bit(EV_KEY, input->evbit); __set_bit(BTN_A, input->keybit); __set_bit(BTN_B, input->keybit); __set_bit(BTN_X, input->keybit); __set_bit(BTN_Y, input->keybit); __set_bit(BTN_START, input->keybit); __set_bit(BTN_SELECT, input->keybit); __set_bit(EV_ABS, input->evbit); input_set_abs_params(input, ABS_X, -1, 1, 0, 0); input_set_abs_params(input, ABS_Y, -1, 1, 0, 0); error = input_register_device(input); if (error) { dev_err(pcu->dev, "Failed to register gamepad input device: %d\n", error); goto err_free_mem; } pcu->gamepad = gamepad; return 0; err_free_mem: input_free_device(input); kfree(gamepad); return -ENOMEM; }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov326100.00%2100.00%
Total326100.00%2100.00%


static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu) { struct ims_pcu_gamepad *gamepad = pcu->gamepad; input_unregister_device(gamepad->input); kfree(gamepad); }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov32100.00%1100.00%
Total32100.00%1100.00%

/********************************************************************* * PCU Communication protocol handling * *********************************************************************/ #define IMS_PCU_PROTOCOL_STX 0x02 #define IMS_PCU_PROTOCOL_ETX 0x03 #define IMS_PCU_PROTOCOL_DLE 0x10 /* PCU commands */ #define IMS_PCU_CMD_STATUS 0xa0 #define IMS_PCU_CMD_PCU_RESET 0xa1 #define IMS_PCU_CMD_RESET_REASON 0xa2 #define IMS_PCU_CMD_SEND_BUTTONS 0xa3 #define IMS_PCU_CMD_JUMP_TO_BTLDR 0xa4 #define IMS_PCU_CMD_GET_INFO 0xa5 #define IMS_PCU_CMD_SET_BRIGHTNESS 0xa6 #define IMS_PCU_CMD_EEPROM 0xa7 #define IMS_PCU_CMD_GET_FW_VERSION 0xa8 #define IMS_PCU_CMD_GET_BL_VERSION 0xa9 #define IMS_PCU_CMD_SET_INFO 0xab #define IMS_PCU_CMD_GET_BRIGHTNESS 0xac #define IMS_PCU_CMD_GET_DEVICE_ID 0xae #define IMS_PCU_CMD_SPECIAL_INFO 0xb0 #define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */ #define IMS_PCU_CMD_OFN_SET_CONFIG 0xb3 #define IMS_PCU_CMD_OFN_GET_CONFIG 0xb4 /* PCU responses */ #define IMS_PCU_RSP_STATUS 0xc0 #define IMS_PCU_RSP_PCU_RESET 0 /* Originally 0xc1 */ #define IMS_PCU_RSP_RESET_REASON 0xc2 #define IMS_PCU_RSP_SEND_BUTTONS 0xc3 #define IMS_PCU_RSP_JUMP_TO_BTLDR 0 /* Originally 0xc4 */ #define IMS_PCU_RSP_GET_INFO 0xc5 #define IMS_PCU_RSP_SET_BRIGHTNESS 0xc6 #define IMS_PCU_RSP_EEPROM 0xc7 #define IMS_PCU_RSP_GET_FW_VERSION 0xc8 #define IMS_PCU_RSP_GET_BL_VERSION 0xc9 #define IMS_PCU_RSP_SET_INFO 0xcb #define IMS_PCU_RSP_GET_BRIGHTNESS 0xcc #define IMS_PCU_RSP_CMD_INVALID 0xcd #define IMS_PCU_RSP_GET_DEVICE_ID 0xce #define IMS_PCU_RSP_SPECIAL_INFO 0xd0 #define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */ #define IMS_PCU_RSP_OFN_SET_CONFIG 0xd2 #define IMS_PCU_RSP_OFN_GET_CONFIG 0xd3 #define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */ #define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */ #define IMS_PCU_MIN_PACKET_LEN 3 #define IMS_PCU_DATA_OFFSET 2 #define IMS_PCU_CMD_WRITE_TIMEOUT 100 /* msec */ #define IMS_PCU_CMD_RESPONSE_TIMEOUT 500 /* msec */
static void ims_pcu_report_events(struct ims_pcu *pcu) { u32 data = get_unaligned_be32(&pcu->read_buf[3]); ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK); if (pcu->gamepad) ims_pcu_gamepad_report(pcu, data); }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov48100.00%1100.00%
Total48100.00%1100.00%


static void ims_pcu_handle_response(struct ims_pcu *pcu) { switch (pcu->read_buf[0]) { case IMS_PCU_RSP_EVNT_BUTTONS: if (likely(pcu->setup_complete)) ims_pcu_report_events(pcu); break; default: /* * See if we got command completion. * If both the sequence and response code match save * the data and signal completion. */ if (pcu->read_buf[0] == pcu->expected_response && pcu->read_buf[1] == pcu->ack_id - 1) { memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos); pcu->cmd_buf_len = pcu->read_pos; complete(&pcu->cmd_done); } break; } }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov102100.00%1100.00%
Total102100.00%1100.00%


static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) { int i; for (i = 0; i < urb->actual_length; i++) { u8 data = pcu->urb_in_buf[i]; /* Skip everything until we get Start Xmit */ if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX) continue; if (pcu->have_dle) { pcu->have_dle = false; pcu->read_buf[pcu->read_pos++] = data; pcu->check_sum += data; continue; } switch (data) { case IMS_PCU_PROTOCOL_STX: if (pcu->have_stx) dev_warn(pcu->dev, "Unexpected STX at byte %d, discarding old data\n", pcu->read_pos); pcu->have_stx = true; pcu->have_dle = false; pcu->read_pos = 0; pcu->check_sum = 0; break; case IMS_PCU_PROTOCOL_DLE: pcu->have_dle = true; break; case IMS_PCU_PROTOCOL_ETX: if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) { dev_warn(pcu->dev, "Short packet received (%d bytes), ignoring\n", pcu->read_pos); } else if (pcu->check_sum != 0) { dev_warn(pcu->dev, "Invalid checksum in packet (%d bytes), ignoring\n", pcu->read_pos); } else { ims_pcu_handle_response(pcu); } pcu->have_stx = false; pcu->have_dle = false; pcu->read_pos = 0; break; default: pcu->read_buf[pcu->read_pos++] = data; pcu->check_sum += data; break; } } }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov252100.00%1100.00%
Total252100.00%1100.00%


static bool ims_pcu_byte_needs_escape(u8 byte) { return byte == IMS_PCU_PROTOCOL_STX || byte == IMS_PCU_PROTOCOL_ETX || byte == IMS_PCU_PROTOCOL_DLE; }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov22100.00%1100.00%
Total22100.00%1100.00%


static int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu, u8 command, int chunk, int len) { int error; error = usb_bulk_msg(pcu->udev, usb_sndbulkpipe(pcu->udev, pcu->ep_out->bEndpointAddress), pcu->urb_out_buf, len, NULL, IMS_PCU_CMD_WRITE_TIMEOUT); if (error < 0) { dev_dbg(pcu->dev, "Sending 0x%02x command failed at chunk %d: %d\n", command, chunk, error); return error; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov84100.00%1100.00%
Total84100.00%1100.00%


static int ims_pcu_send_command(struct ims_pcu *pcu, u8 command, const u8 *data, int len) { int count = 0; int chunk = 0; int delta; int i; int error; u8 csum = 0; u8 ack_id; pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX; /* We know the command need not be escaped */ pcu->urb_out_buf[count++] = command; csum += command; ack_id = pcu->ack_id++; if (ack_id == 0xff) ack_id = pcu->ack_id++; if (ims_pcu_byte_needs_escape(ack_id)) pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; pcu->urb_out_buf[count++] = ack_id; csum += ack_id; for (i = 0; i < len; i++) { delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1; if (count + delta >= pcu->max_out_size) { error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); if (error) return error; count = 0; } if (delta == 2) pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; pcu->urb_out_buf[count++] = data[i]; csum += data[i]; } csum = 1 + ~csum; delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2; if (count + delta >= pcu->max_out_size) { error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); if (error) return error; count = 0; } if (delta == 3) pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; pcu->urb_out_buf[count++] = csum; pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX; return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov331100.00%1100.00%
Total331100.00%1100.00%


static int __ims_pcu_execute_command(struct ims_pcu *pcu, u8 command, const void *data, size_t len, u8 expected_response, int response_time) { int error; pcu->expected_response = expected_response; init_completion(&pcu->cmd_done); error = ims_pcu_send_command(pcu, command, data, len); if (error) return error; if (expected_response && !wait_for_completion_timeout(&pcu->cmd_done, msecs_to_jiffies(response_time))) { dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command); return -ETIMEDOUT; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Torokhov103100.00%1100.00%
Total103100.00%1100.00%

#define ims_pcu_execute_command(pcu, code, data, len) \ __ims_pcu_execute_command(pcu, \ IMS_PCU_CMD_##code, data, len, \ IMS_PCU_RSP_##code, \ IMS_PCU_CMD_RESPONSE_TIMEOUT) #define ims_pcu_execute_query(pcu, code) \ ims_pcu_execute_command(pcu, code, NULL, 0) /* Bootloader commands */ #define IMS_PCU_BL_CMD_QUERY_DEVICE 0xa1 #define IMS_PCU_BL_CMD_UNLOCK_CONFIG 0xa2 #define IMS_PCU_BL_CMD_ERASE_APP 0xa3 #define IMS_PCU_BL_CMD_PROGRAM_DEVICE 0xa4 #define IMS_PCU_BL_CMD_PROGRAM_COMPLETE 0xa5 #define IMS_PCU_BL_CMD_READ_APP 0xa6 #define IMS_PCU_BL_CMD_RESET_DEVICE 0xa7 #define IMS_PCU_BL_CMD_LAUNCH_APP 0xa8 /* Bootloader commands */ #define IMS_PCU_BL_RSP_QUERY_DEVICE 0xc1 #define IMS_PCU_BL_RSP_UNLOCK_CONFIG 0xc2 #define IMS_PCU_BL_RSP_ERASE_APP 0xc3 #define IMS_PCU_BL_RSP_PROGRAM_DEVICE 0xc4 #define IMS_PCU_BL_RSP_PROGRAM_COMPLETE 0xc5 #define IMS_PCU_BL_RSP_READ_APP 0xc6 #define IMS_PCU_BL_RSP_RESET_DEVICE 0 /* originally 0xa7 */ #define IMS_PCU_BL_RSP_LAUNCH_APP 0 /* originally 0xa8 */ #define IMS_PCU_BL_DATA_OFFSET 3
static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu, u8 command, const void *data, size_t len, u8 expected_response, int response_time) { int error; pcu->cmd_buf[0] = command; if (data) memcpy(&pcu->cmd_buf[1], data, len); error = __ims_pcu_execute_command(pcu, IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1, expected_response ? IMS_PCU_RSP_BOOTLOADER : 0, response_time); if (error) { dev_err(pcu->dev, "Failure when sending 0x%02x command to bootloader, error: %d\n", pcu->cmd_buf[0], error); return error; } if (expected_response && pcu->cmd_buf[2] != expected_response)