blob: 7f90d0da6572f4d56a3307f4c0c6afeafee05f8f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* evdi_sysfs.c
*
* Copyright (c) 2020 DisplayLink (UK) Ltd.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include "evdi_sysfs.h"
#include "evdi_params.h"
#include "evdi_debug.h"
#include "evdi_platform_drv.h"
#define MAX_EVDI_USB_ADDR 10
static ssize_t version_show(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u.%u.%u\n", DRIVER_MAJOR,
DRIVER_MINOR, DRIVER_PATCH);
}
static ssize_t count_show(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", evdi_platform_device_count(dev));
}
struct evdi_usb_addr {
int addr[MAX_EVDI_USB_ADDR];
int len;
struct usb_device *usb;
};
static int evdi_platform_device_attach(struct device *device,
struct evdi_usb_addr *parent_addr);
static ssize_t add_device_with_usb_path(struct device *dev,
const char *buf, size_t count)
{
char *usb_path = kstrdup(buf, GFP_KERNEL);
char *temp_path = usb_path;
char *bus_token;
char *usb_token;
char *usb_token_copy = NULL;
char *token;
char *bus;
char *port;
struct evdi_usb_addr usb_addr;
if (!usb_path)
return -ENOMEM;
memset(&usb_addr, 0, sizeof(usb_addr));
temp_path = strnstr(temp_path, "usb:", count);
if (!temp_path)
goto err_parse_usb_path;
temp_path = strim(temp_path);
bus_token = strsep(&temp_path, ":");
if (!bus_token)
goto err_parse_usb_path;
usb_token = strsep(&temp_path, ":");
if (!usb_token)
goto err_parse_usb_path;
/* Separate trailing ':*' from usb_token */
strsep(&temp_path, ":");
token = usb_token_copy = kstrdup(usb_token, GFP_KERNEL);
bus = strsep(&token, "-");
if (!bus)
goto err_parse_usb_path;
if (kstrtouint(bus, 10, &usb_addr.addr[usb_addr.len++]))
goto err_parse_usb_path;
do {
port = strsep(&token, ".");
if (!port)
goto err_parse_usb_path;
if (kstrtouint(port, 10, &usb_addr.addr[usb_addr.len++]))
goto err_parse_usb_path;
} while (token && port && usb_addr.len < MAX_EVDI_USB_ADDR);
if (evdi_platform_device_attach(dev, &usb_addr) != 0) {
EVDI_ERROR("Unable to attach to: %s\n", buf);
kfree(usb_path);
kfree(usb_token_copy);
return -EINVAL;
}
EVDI_INFO("Attaching to %s:%s\n", bus_token, usb_token);
kfree(usb_path);
kfree(usb_token_copy);
return count;
err_parse_usb_path:
EVDI_ERROR("Unable to parse usb path: %s", buf);
kfree(usb_path);
kfree(usb_token_copy);
return -EINVAL;
}
static int find_usb_device_at_path(struct usb_device *usb, void *data)
{
struct evdi_usb_addr *find_path = (struct evdi_usb_addr *)(data);
struct usb_device *pdev = usb;
int port = 0;
int i;
i = find_path->len - 1;
while (pdev != NULL && i >= 0 && i < MAX_EVDI_USB_ADDR) {
port = pdev->portnum;
if (port == 0)
port = pdev->bus->busnum;
if (port != find_path->addr[i])
return 0;
if (pdev->parent == NULL && i == 0) {
find_path->usb = usb;
return 1;
}
pdev = pdev->parent;
i--;
}
return 0;
}
static int evdi_platform_device_attach(struct device *device,
struct evdi_usb_addr *parent_addr)
{
struct device *parent = NULL;
if (!parent_addr)
return -EINVAL;
if (!usb_for_each_dev(parent_addr, find_usb_device_at_path) ||
!parent_addr->usb)
return -EINVAL;
parent = &parent_addr->usb->dev;
return evdi_platform_device_add(device, parent);
}
static ssize_t add_store(struct device *dev,
__always_unused struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int val;
int ret;
if (strnstr(buf, "usb:", count))
return add_device_with_usb_path(dev, buf, count);
if (kstrtouint(buf, 10, &val)) {
EVDI_ERROR("Invalid device count \"%s\"\n", buf);
return -EINVAL;
}
ret = evdi_platform_add_devices(dev, val);
if (ret)
return ret;
return count;
}
static ssize_t remove_all_store(struct device *dev,
__always_unused struct device_attribute *attr,
__always_unused const char *buf,
size_t count)
{
evdi_platform_remove_all_devices(dev);
return count;
}
static ssize_t loglevel_show(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", evdi_loglevel);
}
static ssize_t loglevel_store(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
const char *buf,
size_t count)
{
unsigned int val;
if (kstrtouint(buf, 10, &val)) {
EVDI_ERROR("Unable to parse %u\n", val);
return -EINVAL;
}
if (val > EVDI_LOGLEVEL_VERBOSE) {
EVDI_ERROR("Invalid loglevel %u\n", val);
return -EINVAL;
}
EVDI_INFO("Setting loglevel to %u\n", val);
evdi_loglevel = val;
return count;
}
static struct device_attribute evdi_device_attributes[] = {
__ATTR_RO(count),
__ATTR_RO(version),
__ATTR_RW(loglevel),
__ATTR_WO(add),
__ATTR_WO(remove_all)
};
void evdi_sysfs_init(struct device *root)
{
unsigned int i;
if (!PTR_ERR_OR_ZERO(root))
for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++)
device_create_file(root, &evdi_device_attributes[i]);
}
void evdi_sysfs_exit(struct device *root)
{
unsigned int i;
if (PTR_ERR_OR_ZERO(root)) {
EVDI_ERROR("root device is null");
return;
}
for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++)
device_remove_file(root, &evdi_device_attributes[i]);
}