| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 - 2021 DisplayLink (UK) Ltd. |
| */ |
| #include <linux/module.h> |
| |
| #include "hcd.h" |
| #include "mausb_address.h" |
| #include "utils.h" |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("DisplayLink (UK) Ltd."); |
| |
| static struct mausb_device_address device_address; |
| static int mausb_device_disconnect_param; |
| static u16 madev_addr; |
| static u8 mausb_client_connect_param; |
| static u8 mausb_client_disconnect_param; |
| |
| static int mausb_client_connect(const char *value, |
| const struct kernel_param *kp) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&mss.lock, flags); |
| if (mss.client_connected) { |
| dev_err(mausb_host_dev.this_device, "MA-USB client is already connected"); |
| spin_unlock_irqrestore(&mss.lock, flags); |
| return -EEXIST; |
| } |
| /* Save heartbeat client information */ |
| mss.client_connected = true; |
| mss.missed_heartbeats = 0; |
| reinit_completion(&mss.client_stopped); |
| spin_unlock_irqrestore(&mss.lock, flags); |
| /* Start hearbeat timer */ |
| mod_timer(&mss.heartbeat_timer, |
| jiffies + msecs_to_jiffies(MAUSB_HEARTBEAT_TIMEOUT_MS)); |
| |
| return 0; |
| } |
| |
| static int mausb_client_disconnect(const char *value, |
| const struct kernel_param *kp) |
| { |
| unsigned long flags; |
| struct mausb_device *dev = NULL; |
| |
| spin_lock_irqsave(&mss.lock, flags); |
| if (!mss.client_connected) { |
| dev_err(mausb_host_dev.this_device, "MA-USB client is not connected"); |
| spin_unlock_irqrestore(&mss.lock, flags); |
| return -ENODEV; |
| } |
| spin_unlock_irqrestore(&mss.lock, flags); |
| |
| /* Stop heartbeat timer */ |
| del_timer_sync(&mss.heartbeat_timer); |
| |
| /* Clear heartbeat client information */ |
| spin_lock_irqsave(&mss.lock, flags); |
| mss.client_connected = false; |
| mss.missed_heartbeats = 0; |
| list_for_each_entry(dev, &mss.madev_list, list_entry) { |
| dev_vdbg(mausb_host_dev.this_device, "Enqueue heartbeat_work madev_addr=%x", |
| dev->madev_addr); |
| queue_work(dev->workq, &dev->heartbeat_work); |
| } |
| complete(&mss.client_stopped); |
| spin_unlock_irqrestore(&mss.lock, flags); |
| |
| return 0; |
| } |
| |
| static int mausb_device_connect(const char *value, |
| const struct kernel_param *kp) |
| { |
| int status = 0; |
| |
| if (!value) |
| return -EINVAL; |
| |
| if (strlen(value) <= INET6_ADDRSTRLEN) { |
| strcpy(device_address.ip.address, value); |
| dev_info(mausb_host_dev.this_device, "Processing '%s' address", |
| device_address.ip.address); |
| } else { |
| dev_err(mausb_host_dev.this_device, "Invalid IP format"); |
| return 0; |
| } |
| status = mausb_initiate_dev_connection(device_address, madev_addr); |
| memset(&device_address, 0, sizeof(device_address)); |
| |
| return status; |
| } |
| |
| static int mausb_device_disconnect(const char *value, |
| const struct kernel_param *kp) |
| { |
| u8 dev_address = 0; |
| int status = 0; |
| unsigned long flags; |
| struct mausb_device *dev = NULL; |
| |
| if (!value) |
| return -EINVAL; |
| |
| status = kstrtou8(value, 0, &dev_address); |
| if (status < 0) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&mss.lock, flags); |
| |
| dev = mausb_get_dev_from_addr_unsafe(dev_address); |
| if (dev) |
| queue_work(dev->workq, &dev->hcd_disconnect_work); |
| |
| spin_unlock_irqrestore(&mss.lock, flags); |
| |
| return 0; |
| } |
| |
| static const struct kernel_param_ops mausb_device_connect_ops = { |
| .set = mausb_device_connect |
| }; |
| |
| static const struct kernel_param_ops mausb_device_disconnect_ops = { |
| .set = mausb_device_disconnect |
| }; |
| |
| static const struct kernel_param_ops mausb_client_connect_ops = { |
| .set = mausb_client_connect |
| }; |
| |
| static const struct kernel_param_ops mausb_client_disconnect_ops = { |
| .set = mausb_client_disconnect |
| }; |
| |
| module_param_named(mgmt, device_address.ip.port.management, ushort, 0664); |
| MODULE_PARM_DESC(mgmt, "MA-USB management port"); |
| module_param_named(ctrl, device_address.ip.port.control, ushort, 0664); |
| MODULE_PARM_DESC(ctrl, "MA-USB control port"); |
| module_param_named(bulk, device_address.ip.port.bulk, ushort, 0664); |
| MODULE_PARM_DESC(bulk, "MA-USB bulk port"); |
| module_param_named(isoch, device_address.ip.port.isochronous, ushort, 0664); |
| MODULE_PARM_DESC(isoch, "MA-USB isochronous port"); |
| module_param_named(madev_addr, madev_addr, ushort, 0664); |
| MODULE_PARM_DESC(madev_addr, "MA-USB device address"); |
| |
| module_param_cb(client_connect, &mausb_client_connect_ops, |
| &mausb_client_connect_param, 0664); |
| module_param_cb(client_disconnect, &mausb_client_disconnect_ops, |
| &mausb_client_disconnect_param, 0664); |
| module_param_cb(ip, &mausb_device_connect_ops, |
| device_address.ip.address, 0664); |
| module_param_cb(disconnect, &mausb_device_disconnect_ops, |
| &mausb_device_disconnect_param, 0664); |
| |
| static int mausb_host_init(void) |
| { |
| int status = mausb_host_dev_register(); |
| |
| if (status < 0) |
| return status; |
| |
| status = mausb_host_driver_init(); |
| if (status < 0) |
| goto cleanup_dev; |
| |
| status = mausb_register_power_state_listener(); |
| if (status < 0) |
| goto cleanup; |
| |
| mausb_initialize_mss(); |
| |
| return 0; |
| |
| cleanup: |
| mausb_host_driver_deinit(); |
| cleanup_dev: |
| mausb_host_dev_deregister(); |
| return status; |
| } |
| |
| static void mausb_host_exit(void) |
| { |
| mausb_unregister_power_state_listener(); |
| mausb_deinitialize_mss(); |
| mausb_host_driver_deinit(); |
| mausb_host_dev_deregister(); |
| } |
| |
| module_init(mausb_host_init); |
| module_exit(mausb_host_exit); |