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, simply emit a log message and continue 129 * because product information is not critical for the driver to 130 * operate. 131 */ 132 void es58x_parse_product_info(struct es58x_device *es58x_dev) 133 { 134 char *prod_info; 135 136 prod_info = usb_cache_string(es58x_dev->udev, ES58X_PROD_INFO_IDX); 137 if (!prod_info) { 138 dev_warn(es58x_dev->dev, 139 "could not retrieve the product info string\n"); 140 return; 141 } 142 143 if (es58x_parse_sw_version(es58x_dev, prod_info, "FW") || 144 es58x_parse_sw_version(es58x_dev, prod_info, "BL") || 145 es58x_parse_hw_rev(es58x_dev, prod_info)) 146 dev_info(es58x_dev->dev, 147 "could not parse product info: '%s'\n", prod_info); 148 149 kfree(prod_info); 150 } 151 152 /** 153 * es58x_sw_version_is_set() - Check if the version is a valid number. 154 * @sw_ver: Version number of either the firmware or the bootloader. 155 * 156 * If &es58x_sw_version.major, &es58x_sw_version.minor and 157 * &es58x_sw_version.revision are all zero, the product string could 158 * not be parsed and the version number is invalid. 159 */ 160 static inline bool es58x_sw_version_is_set(struct es58x_sw_version *sw_ver) 161 { 162 return sw_ver->major || sw_ver->minor || sw_ver->revision; 163 } 164 165 /** 166 * es58x_hw_revision_is_set() - Check if the revision is a valid number. 167 * @hw_rev: Revision number of the hardware. 168 * 169 * If &es58x_hw_revision.letter is the null character, the product 170 * string could not be parsed and the hardware revision number is 171 * invalid. 172 */ 173 static inline bool es58x_hw_revision_is_set(struct es58x_hw_revision *hw_rev) 174 { 175 return hw_rev->letter != '\0'; 176 } 177 178 /** 179 * es58x_devlink_info_get() - Report the product information. 180 * @devlink: Devlink. 181 * @req: skb wrapper where to put requested information. 182 * @extack: Unused. 183 * 184 * Report the firmware version, the bootloader version, the hardware 185 * revision and the serial number through netlink. 186 * 187 * Return: zero on success, errno when any error occurs. 188 */ 189 static int es58x_devlink_info_get(struct devlink *devlink, 190 struct devlink_info_req *req, 191 struct netlink_ext_ack *extack) 192 { 193 struct es58x_device *es58x_dev = devlink_priv(devlink); 194 struct es58x_sw_version *fw_ver = &es58x_dev->firmware_version; 195 struct es58x_sw_version *bl_ver = &es58x_dev->bootloader_version; 196 struct es58x_hw_revision *hw_rev = &es58x_dev->hardware_revision; 197 char buf[max(sizeof("xx.xx.xx"), sizeof("axxx/xxx"))]; 198 int ret = 0; 199 200 if (es58x_sw_version_is_set(fw_ver)) { 201 snprintf(buf, sizeof(buf), "%02u.%02u.%02u", 202 fw_ver->major, fw_ver->minor, fw_ver->revision); 203 ret = devlink_info_version_running_put(req, 204 DEVLINK_INFO_VERSION_GENERIC_FW, 205 buf); 206 if (ret) 207 return ret; 208 } 209 210 if (es58x_sw_version_is_set(bl_ver)) { 211 snprintf(buf, sizeof(buf), "%02u.%02u.%02u", 212 bl_ver->major, bl_ver->minor, bl_ver->revision); 213 ret = devlink_info_version_running_put(req, 214 DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER, 215 buf); 216 if (ret) 217 return ret; 218 } 219 220 if (es58x_hw_revision_is_set(hw_rev)) { 221 snprintf(buf, sizeof(buf), "%c%03u/%03u", 222 hw_rev->letter, hw_rev->major, hw_rev->minor); 223 ret = devlink_info_version_fixed_put(req, 224 DEVLINK_INFO_VERSION_GENERIC_BOARD_REV, 225 buf); 226 if (ret) 227 return ret; 228 } 229 230 return devlink_info_serial_number_put(req, es58x_dev->udev->serial); 231 } 232 233 const struct devlink_ops es58x_dl_ops = { 234 .info_get = es58x_devlink_info_get, 235 }; 236