1 // SPDX-License-Identifier: GPL-2.0 2 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 6 19 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 the 26 * bootloader version. 27 * 28 * The @prod_info string contains the firmware and the bootloader 29 * version number all prefixed by a magic string and concatenated with 30 * 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 must 33 * contains the common part of those prefixes: "FW" or "BL". 34 * 35 * Parse @prod_info and store the version number in 36 * &es58x_dev.firmware_version or &es58x_dev.bootloader_version 37 * according to @prefix value. 38 * 39 * Return: zero on success, -EINVAL if @prefix contains an invalid 40 * value and -EBADMSG if @prod_info could not be parsed. 41 */ 42 static 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 else 53 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 magic 82 * string and conquenated together with other numbers. Depending on 83 * the device, the hardware revision format is either 84 * "HW_VER:axxx/xxx" or "HR:axxx/xxx" where 'a' represents a letter 85 * and 'x' a digit. 86 * 87 * Parse @prod_info and store the hardware revision number in 88 * &es58x_dev.hardware_revision. 89 * 90 * Return: zero on success, -EBADMSG if @prod_info could not be 91 * parsed. 92 */ 93 static 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 information 121 * string. 122 * @es58x_dev: ES58X device. 123 * 124 * Retrieve the product information string and parse it to extract the 125 * firmware version, the bootloader version and the hardware 126 * revision. 127 * 128 * If the function fails, set the version or revision to an invalid 129 * value and emit an informal message. Continue probing because the 130 * product information is not critical for the driver to operate. 131 */ 132 void 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 two 171 * digits, the version is invalid, most probably because the product 172 * string could not be parsed. 173 * 174 * Return: @true if the software version is valid, @false otherwise. 175 */ 176 static 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 if 187 * any of the hardware revision sub-numbers do not fit on three 188 * digits, the revision is invalid, most probably because the product 189 * string could not be parsed. 190 * 191 * Return: @true if the hardware revision is valid, @false otherwise. 192 */ 193 static 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 hardware 206 * revision and the serial number through netlink. 207 * 208 * Return: zero on success, errno when any error occurs. 209 */ 210 static 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 254 const struct devlink_ops es58x_dl_ops = { 255 .info_get = es58x_devlink_info_get, 256 }; 257