257 lines · c
1// SPDX-License-Identifier: GPL-2.02 3/* Driver for ETAS GmbH ES58X USB CAN(-FD) Bus Interfaces.4 *5 * File es58x_devlink.c: report the product information using devlink.6 *7 * Copyright (c) 2022 Vincent Mailhol <mailhol.vincent@wanadoo.fr>8 */9 10#include <linux/ctype.h>11#include <linux/device.h>12#include <linux/usb.h>13#include <net/devlink.h>14 15#include "es58x_core.h"16 17/* USB descriptor index containing the product information string. */18#define ES58X_PROD_INFO_IDX 619 20/**21 * es58x_parse_sw_version() - Extract boot loader or firmware version.22 * @es58x_dev: ES58X device.23 * @prod_info: USB custom string returned by the device.24 * @prefix: Select which information should be parsed. Set it to "FW"25 * to parse the firmware version or to "BL" to parse the26 * bootloader version.27 *28 * The @prod_info string contains the firmware and the bootloader29 * version number all prefixed by a magic string and concatenated with30 * other numbers. Depending on the device, the firmware (bootloader)31 * format is either "FW_Vxx.xx.xx" ("BL_Vxx.xx.xx") or "FW:xx.xx.xx"32 * ("BL:xx.xx.xx") where 'x' represents a digit. @prod_info must33 * contains the common part of those prefixes: "FW" or "BL".34 *35 * Parse @prod_info and store the version number in36 * &es58x_dev.firmware_version or &es58x_dev.bootloader_version37 * according to @prefix value.38 *39 * Return: zero on success, -EINVAL if @prefix contains an invalid40 * value and -EBADMSG if @prod_info could not be parsed.41 */42static int es58x_parse_sw_version(struct es58x_device *es58x_dev,43 const char *prod_info, const char *prefix)44{45 struct es58x_sw_version *version;46 int major, minor, revision;47 48 if (!strcmp(prefix, "FW"))49 version = &es58x_dev->firmware_version;50 else if (!strcmp(prefix, "BL"))51 version = &es58x_dev->bootloader_version;52 else53 return -EINVAL;54 55 /* Go to prefix */56 prod_info = strstr(prod_info, prefix);57 if (!prod_info)58 return -EBADMSG;59 /* Go to beginning of the version number */60 while (!isdigit(*prod_info)) {61 prod_info++;62 if (!*prod_info)63 return -EBADMSG;64 }65 66 if (sscanf(prod_info, "%2u.%2u.%2u", &major, &minor, &revision) != 3)67 return -EBADMSG;68 69 version->major = major;70 version->minor = minor;71 version->revision = revision;72 73 return 0;74}75 76/**77 * es58x_parse_hw_rev() - Extract hardware revision number.78 * @es58x_dev: ES58X device.79 * @prod_info: USB custom string returned by the device.80 *81 * @prod_info contains the hardware revision prefixed by a magic82 * string and conquenated together with other numbers. Depending on83 * the device, the hardware revision format is either84 * "HW_VER:axxx/xxx" or "HR:axxx/xxx" where 'a' represents a letter85 * and 'x' a digit.86 *87 * Parse @prod_info and store the hardware revision number in88 * &es58x_dev.hardware_revision.89 *90 * Return: zero on success, -EBADMSG if @prod_info could not be91 * parsed.92 */93static int es58x_parse_hw_rev(struct es58x_device *es58x_dev,94 const char *prod_info)95{96 char letter;97 int major, minor;98 99 /* The only occurrence of 'H' is in the hardware revision prefix. */100 prod_info = strchr(prod_info, 'H');101 if (!prod_info)102 return -EBADMSG;103 /* Go to beginning of the hardware revision */104 prod_info = strchr(prod_info, ':');105 if (!prod_info)106 return -EBADMSG;107 prod_info++;108 109 if (sscanf(prod_info, "%c%3u/%3u", &letter, &major, &minor) != 3)110 return -EBADMSG;111 112 es58x_dev->hardware_revision.letter = letter;113 es58x_dev->hardware_revision.major = major;114 es58x_dev->hardware_revision.minor = minor;115 116 return 0;117}118 119/**120 * es58x_parse_product_info() - Parse the ES58x product information121 * string.122 * @es58x_dev: ES58X device.123 *124 * Retrieve the product information string and parse it to extract the125 * firmware version, the bootloader version and the hardware126 * revision.127 *128 * If the function fails, set the version or revision to an invalid129 * value and emit an informal message. Continue probing because the130 * product information is not critical for the driver to operate.131 */132void es58x_parse_product_info(struct es58x_device *es58x_dev)133{134 static const struct es58x_sw_version sw_version_not_set = {135 .major = -1,136 .minor = -1,137 .revision = -1,138 };139 static const struct es58x_hw_revision hw_revision_not_set = {140 .letter = '\0',141 .major = -1,142 .minor = -1,143 };144 char *prod_info;145 146 es58x_dev->firmware_version = sw_version_not_set;147 es58x_dev->bootloader_version = sw_version_not_set;148 es58x_dev->hardware_revision = hw_revision_not_set;149 150 prod_info = usb_cache_string(es58x_dev->udev, ES58X_PROD_INFO_IDX);151 if (!prod_info) {152 dev_warn(es58x_dev->dev,153 "could not retrieve the product info string\n");154 return;155 }156 157 if (es58x_parse_sw_version(es58x_dev, prod_info, "FW") ||158 es58x_parse_sw_version(es58x_dev, prod_info, "BL") ||159 es58x_parse_hw_rev(es58x_dev, prod_info))160 dev_info(es58x_dev->dev,161 "could not parse product info: '%s'\n", prod_info);162 163 kfree(prod_info);164}165 166/**167 * es58x_sw_version_is_valid() - Check if the version is a valid number.168 * @sw_ver: Version number of either the firmware or the bootloader.169 *170 * If any of the software version sub-numbers do not fit on two171 * digits, the version is invalid, most probably because the product172 * string could not be parsed.173 *174 * Return: @true if the software version is valid, @false otherwise.175 */176static inline bool es58x_sw_version_is_valid(struct es58x_sw_version *sw_ver)177{178 return sw_ver->major < 100 && sw_ver->minor < 100 &&179 sw_ver->revision < 100;180}181 182/**183 * es58x_hw_revision_is_valid() - Check if the revision is a valid number.184 * @hw_rev: Revision number of the hardware.185 *186 * If &es58x_hw_revision.letter is not a alphanumeric character or if187 * any of the hardware revision sub-numbers do not fit on three188 * digits, the revision is invalid, most probably because the product189 * string could not be parsed.190 *191 * Return: @true if the hardware revision is valid, @false otherwise.192 */193static inline bool es58x_hw_revision_is_valid(struct es58x_hw_revision *hw_rev)194{195 return isalnum(hw_rev->letter) && hw_rev->major < 1000 &&196 hw_rev->minor < 1000;197}198 199/**200 * es58x_devlink_info_get() - Report the product information.201 * @devlink: Devlink.202 * @req: skb wrapper where to put requested information.203 * @extack: Unused.204 *205 * Report the firmware version, the bootloader version, the hardware206 * revision and the serial number through netlink.207 *208 * Return: zero on success, errno when any error occurs.209 */210static int es58x_devlink_info_get(struct devlink *devlink,211 struct devlink_info_req *req,212 struct netlink_ext_ack *extack)213{214 struct es58x_device *es58x_dev = devlink_priv(devlink);215 struct es58x_sw_version *fw_ver = &es58x_dev->firmware_version;216 struct es58x_sw_version *bl_ver = &es58x_dev->bootloader_version;217 struct es58x_hw_revision *hw_rev = &es58x_dev->hardware_revision;218 char buf[MAX(sizeof("xx.xx.xx"), sizeof("axxx/xxx"))];219 int ret = 0;220 221 if (es58x_sw_version_is_valid(fw_ver)) {222 snprintf(buf, sizeof(buf), "%02u.%02u.%02u",223 fw_ver->major, fw_ver->minor, fw_ver->revision);224 ret = devlink_info_version_running_put(req,225 DEVLINK_INFO_VERSION_GENERIC_FW,226 buf);227 if (ret)228 return ret;229 }230 231 if (es58x_sw_version_is_valid(bl_ver)) {232 snprintf(buf, sizeof(buf), "%02u.%02u.%02u",233 bl_ver->major, bl_ver->minor, bl_ver->revision);234 ret = devlink_info_version_running_put(req,235 DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER,236 buf);237 if (ret)238 return ret;239 }240 241 if (es58x_hw_revision_is_valid(hw_rev)) {242 snprintf(buf, sizeof(buf), "%c%03u/%03u",243 hw_rev->letter, hw_rev->major, hw_rev->minor);244 ret = devlink_info_version_fixed_put(req,245 DEVLINK_INFO_VERSION_GENERIC_BOARD_REV,246 buf);247 if (ret)248 return ret;249 }250 251 return devlink_info_serial_number_put(req, es58x_dev->udev->serial);252}253 254const struct devlink_ops es58x_dl_ops = {255 .info_get = es58x_devlink_info_get,256};257