720 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Greybus Firmware Management Protocol Driver.4 *5 * Copyright 2016 Google Inc.6 * Copyright 2016 Linaro Ltd.7 */8 9#include <linux/cdev.h>10#include <linux/completion.h>11#include <linux/firmware.h>12#include <linux/fs.h>13#include <linux/idr.h>14#include <linux/ioctl.h>15#include <linux/uaccess.h>16#include <linux/greybus.h>17 18#include "firmware.h"19#include "greybus_firmware.h"20 21#define FW_MGMT_TIMEOUT_MS 100022 23struct fw_mgmt {24 struct device *parent;25 struct gb_connection *connection;26 struct kref kref;27 struct list_head node;28 29 /* Common id-map for interface and backend firmware requests */30 struct ida id_map;31 struct mutex mutex;32 struct completion completion;33 struct cdev cdev;34 struct device *class_device;35 dev_t dev_num;36 unsigned int timeout_jiffies;37 bool disabled; /* connection getting disabled */38 39 /* Interface Firmware specific fields */40 bool mode_switch_started;41 bool intf_fw_loaded;42 u8 intf_fw_request_id;43 u8 intf_fw_status;44 u16 intf_fw_major;45 u16 intf_fw_minor;46 47 /* Backend Firmware specific fields */48 u8 backend_fw_request_id;49 u8 backend_fw_status;50};51 52/*53 * Number of minor devices this driver supports.54 * There will be exactly one required per Interface.55 */56#define NUM_MINORS U8_MAX57 58static const struct class fw_mgmt_class = {59 .name = "gb_fw_mgmt",60};61 62static dev_t fw_mgmt_dev_num;63static DEFINE_IDA(fw_mgmt_minors_map);64static LIST_HEAD(fw_mgmt_list);65static DEFINE_MUTEX(list_mutex);66 67static void fw_mgmt_kref_release(struct kref *kref)68{69 struct fw_mgmt *fw_mgmt = container_of(kref, struct fw_mgmt, kref);70 71 ida_destroy(&fw_mgmt->id_map);72 kfree(fw_mgmt);73}74 75/*76 * All users of fw_mgmt take a reference (from within list_mutex lock), before77 * they get a pointer to play with. And the structure will be freed only after78 * the last user has put the reference to it.79 */80static void put_fw_mgmt(struct fw_mgmt *fw_mgmt)81{82 kref_put(&fw_mgmt->kref, fw_mgmt_kref_release);83}84 85/* Caller must call put_fw_mgmt() after using struct fw_mgmt */86static struct fw_mgmt *get_fw_mgmt(struct cdev *cdev)87{88 struct fw_mgmt *fw_mgmt;89 90 mutex_lock(&list_mutex);91 92 list_for_each_entry(fw_mgmt, &fw_mgmt_list, node) {93 if (&fw_mgmt->cdev == cdev) {94 kref_get(&fw_mgmt->kref);95 goto unlock;96 }97 }98 99 fw_mgmt = NULL;100 101unlock:102 mutex_unlock(&list_mutex);103 104 return fw_mgmt;105}106 107static int fw_mgmt_interface_fw_version_operation(struct fw_mgmt *fw_mgmt,108 struct fw_mgmt_ioc_get_intf_version *fw_info)109{110 struct gb_connection *connection = fw_mgmt->connection;111 struct gb_fw_mgmt_interface_fw_version_response response;112 int ret;113 114 ret = gb_operation_sync(connection,115 GB_FW_MGMT_TYPE_INTERFACE_FW_VERSION, NULL, 0,116 &response, sizeof(response));117 if (ret) {118 dev_err(fw_mgmt->parent,119 "failed to get interface firmware version (%d)\n", ret);120 return ret;121 }122 123 fw_info->major = le16_to_cpu(response.major);124 fw_info->minor = le16_to_cpu(response.minor);125 126 strscpy_pad(fw_info->firmware_tag, response.firmware_tag);127 128 /*129 * The firmware-tag should be NULL terminated, otherwise throw error but130 * don't fail.131 */132 if (fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {133 dev_err(fw_mgmt->parent,134 "fw-version: firmware-tag is not NULL terminated\n");135 fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] = '\0';136 }137 138 return 0;139}140 141static int fw_mgmt_load_and_validate_operation(struct fw_mgmt *fw_mgmt,142 u8 load_method, const char *tag)143{144 struct gb_fw_mgmt_load_and_validate_fw_request request;145 int ret;146 147 if (load_method != GB_FW_LOAD_METHOD_UNIPRO &&148 load_method != GB_FW_LOAD_METHOD_INTERNAL) {149 dev_err(fw_mgmt->parent,150 "invalid load-method (%d)\n", load_method);151 return -EINVAL;152 }153 154 request.load_method = load_method;155 strscpy_pad(request.firmware_tag, tag);156 157 /*158 * The firmware-tag should be NULL terminated, otherwise throw error and159 * fail.160 */161 if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {162 dev_err(fw_mgmt->parent, "load-and-validate: firmware-tag is not NULL terminated\n");163 return -EINVAL;164 }165 166 /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */167 ret = ida_alloc_range(&fw_mgmt->id_map, 1, 255, GFP_KERNEL);168 if (ret < 0) {169 dev_err(fw_mgmt->parent, "failed to allocate request id (%d)\n",170 ret);171 return ret;172 }173 174 fw_mgmt->intf_fw_request_id = ret;175 fw_mgmt->intf_fw_loaded = false;176 request.request_id = ret;177 178 ret = gb_operation_sync(fw_mgmt->connection,179 GB_FW_MGMT_TYPE_LOAD_AND_VALIDATE_FW, &request,180 sizeof(request), NULL, 0);181 if (ret) {182 ida_free(&fw_mgmt->id_map, fw_mgmt->intf_fw_request_id);183 fw_mgmt->intf_fw_request_id = 0;184 dev_err(fw_mgmt->parent,185 "load and validate firmware request failed (%d)\n",186 ret);187 return ret;188 }189 190 return 0;191}192 193static int fw_mgmt_interface_fw_loaded_operation(struct gb_operation *op)194{195 struct gb_connection *connection = op->connection;196 struct fw_mgmt *fw_mgmt = gb_connection_get_data(connection);197 struct gb_fw_mgmt_loaded_fw_request *request;198 199 /* No pending load and validate request ? */200 if (!fw_mgmt->intf_fw_request_id) {201 dev_err(fw_mgmt->parent,202 "unexpected firmware loaded request received\n");203 return -ENODEV;204 }205 206 if (op->request->payload_size != sizeof(*request)) {207 dev_err(fw_mgmt->parent, "illegal size of firmware loaded request (%zu != %zu)\n",208 op->request->payload_size, sizeof(*request));209 return -EINVAL;210 }211 212 request = op->request->payload;213 214 /* Invalid request-id ? */215 if (request->request_id != fw_mgmt->intf_fw_request_id) {216 dev_err(fw_mgmt->parent, "invalid request id for firmware loaded request (%02u != %02u)\n",217 fw_mgmt->intf_fw_request_id, request->request_id);218 return -ENODEV;219 }220 221 ida_free(&fw_mgmt->id_map, fw_mgmt->intf_fw_request_id);222 fw_mgmt->intf_fw_request_id = 0;223 fw_mgmt->intf_fw_status = request->status;224 fw_mgmt->intf_fw_major = le16_to_cpu(request->major);225 fw_mgmt->intf_fw_minor = le16_to_cpu(request->minor);226 227 if (fw_mgmt->intf_fw_status == GB_FW_LOAD_STATUS_FAILED)228 dev_err(fw_mgmt->parent,229 "failed to load interface firmware, status:%02x\n",230 fw_mgmt->intf_fw_status);231 else if (fw_mgmt->intf_fw_status == GB_FW_LOAD_STATUS_VALIDATION_FAILED)232 dev_err(fw_mgmt->parent,233 "failed to validate interface firmware, status:%02x\n",234 fw_mgmt->intf_fw_status);235 else236 fw_mgmt->intf_fw_loaded = true;237 238 complete(&fw_mgmt->completion);239 240 return 0;241}242 243static int fw_mgmt_backend_fw_version_operation(struct fw_mgmt *fw_mgmt,244 struct fw_mgmt_ioc_get_backend_version *fw_info)245{246 struct gb_connection *connection = fw_mgmt->connection;247 struct gb_fw_mgmt_backend_fw_version_request request;248 struct gb_fw_mgmt_backend_fw_version_response response;249 int ret;250 251 strscpy_pad(request.firmware_tag, fw_info->firmware_tag);252 253 /*254 * The firmware-tag should be NULL terminated, otherwise throw error and255 * fail.256 */257 if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {258 dev_err(fw_mgmt->parent, "backend-version: firmware-tag is not NULL terminated\n");259 return -EINVAL;260 }261 262 ret = gb_operation_sync(connection,263 GB_FW_MGMT_TYPE_BACKEND_FW_VERSION, &request,264 sizeof(request), &response, sizeof(response));265 if (ret) {266 dev_err(fw_mgmt->parent, "failed to get version of %s backend firmware (%d)\n",267 fw_info->firmware_tag, ret);268 return ret;269 }270 271 fw_info->status = response.status;272 273 /* Reset version as that should be non-zero only for success case */274 fw_info->major = 0;275 fw_info->minor = 0;276 277 switch (fw_info->status) {278 case GB_FW_BACKEND_VERSION_STATUS_SUCCESS:279 fw_info->major = le16_to_cpu(response.major);280 fw_info->minor = le16_to_cpu(response.minor);281 break;282 case GB_FW_BACKEND_VERSION_STATUS_NOT_AVAILABLE:283 case GB_FW_BACKEND_VERSION_STATUS_RETRY:284 break;285 case GB_FW_BACKEND_VERSION_STATUS_NOT_SUPPORTED:286 dev_err(fw_mgmt->parent,287 "Firmware with tag %s is not supported by Interface\n",288 fw_info->firmware_tag);289 break;290 default:291 dev_err(fw_mgmt->parent, "Invalid status received: %u\n",292 fw_info->status);293 }294 295 return 0;296}297 298static int fw_mgmt_backend_fw_update_operation(struct fw_mgmt *fw_mgmt,299 char *tag)300{301 struct gb_fw_mgmt_backend_fw_update_request request;302 int ret;303 304 ret = strscpy_pad(request.firmware_tag, tag);305 306 /*307 * The firmware-tag should be NULL terminated, otherwise throw error and308 * fail.309 */310 if (ret == -E2BIG) {311 dev_err(fw_mgmt->parent, "backend-update: firmware-tag is not NULL terminated\n");312 return -EINVAL;313 }314 315 /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */316 ret = ida_alloc_range(&fw_mgmt->id_map, 1, 255, GFP_KERNEL);317 if (ret < 0) {318 dev_err(fw_mgmt->parent, "failed to allocate request id (%d)\n",319 ret);320 return ret;321 }322 323 fw_mgmt->backend_fw_request_id = ret;324 request.request_id = ret;325 326 ret = gb_operation_sync(fw_mgmt->connection,327 GB_FW_MGMT_TYPE_BACKEND_FW_UPDATE, &request,328 sizeof(request), NULL, 0);329 if (ret) {330 ida_free(&fw_mgmt->id_map, fw_mgmt->backend_fw_request_id);331 fw_mgmt->backend_fw_request_id = 0;332 dev_err(fw_mgmt->parent,333 "backend %s firmware update request failed (%d)\n", tag,334 ret);335 return ret;336 }337 338 return 0;339}340 341static int fw_mgmt_backend_fw_updated_operation(struct gb_operation *op)342{343 struct gb_connection *connection = op->connection;344 struct fw_mgmt *fw_mgmt = gb_connection_get_data(connection);345 struct gb_fw_mgmt_backend_fw_updated_request *request;346 347 /* No pending load and validate request ? */348 if (!fw_mgmt->backend_fw_request_id) {349 dev_err(fw_mgmt->parent, "unexpected backend firmware updated request received\n");350 return -ENODEV;351 }352 353 if (op->request->payload_size != sizeof(*request)) {354 dev_err(fw_mgmt->parent, "illegal size of backend firmware updated request (%zu != %zu)\n",355 op->request->payload_size, sizeof(*request));356 return -EINVAL;357 }358 359 request = op->request->payload;360 361 /* Invalid request-id ? */362 if (request->request_id != fw_mgmt->backend_fw_request_id) {363 dev_err(fw_mgmt->parent, "invalid request id for backend firmware updated request (%02u != %02u)\n",364 fw_mgmt->backend_fw_request_id, request->request_id);365 return -ENODEV;366 }367 368 ida_free(&fw_mgmt->id_map, fw_mgmt->backend_fw_request_id);369 fw_mgmt->backend_fw_request_id = 0;370 fw_mgmt->backend_fw_status = request->status;371 372 if ((fw_mgmt->backend_fw_status != GB_FW_BACKEND_FW_STATUS_SUCCESS) &&373 (fw_mgmt->backend_fw_status != GB_FW_BACKEND_FW_STATUS_RETRY))374 dev_err(fw_mgmt->parent,375 "failed to load backend firmware: %02x\n",376 fw_mgmt->backend_fw_status);377 378 complete(&fw_mgmt->completion);379 380 return 0;381}382 383/* Char device fops */384 385static int fw_mgmt_open(struct inode *inode, struct file *file)386{387 struct fw_mgmt *fw_mgmt = get_fw_mgmt(inode->i_cdev);388 389 /* fw_mgmt structure can't get freed until file descriptor is closed */390 if (fw_mgmt) {391 file->private_data = fw_mgmt;392 return 0;393 }394 395 return -ENODEV;396}397 398static int fw_mgmt_release(struct inode *inode, struct file *file)399{400 struct fw_mgmt *fw_mgmt = file->private_data;401 402 put_fw_mgmt(fw_mgmt);403 return 0;404}405 406static int fw_mgmt_ioctl(struct fw_mgmt *fw_mgmt, unsigned int cmd,407 void __user *buf)408{409 struct fw_mgmt_ioc_get_intf_version intf_fw_info;410 struct fw_mgmt_ioc_get_backend_version backend_fw_info;411 struct fw_mgmt_ioc_intf_load_and_validate intf_load;412 struct fw_mgmt_ioc_backend_fw_update backend_update;413 unsigned int timeout;414 int ret;415 416 /* Reject any operations after mode-switch has started */417 if (fw_mgmt->mode_switch_started)418 return -EBUSY;419 420 switch (cmd) {421 case FW_MGMT_IOC_GET_INTF_FW:422 ret = fw_mgmt_interface_fw_version_operation(fw_mgmt,423 &intf_fw_info);424 if (ret)425 return ret;426 427 if (copy_to_user(buf, &intf_fw_info, sizeof(intf_fw_info)))428 return -EFAULT;429 430 return 0;431 case FW_MGMT_IOC_GET_BACKEND_FW:432 if (copy_from_user(&backend_fw_info, buf,433 sizeof(backend_fw_info)))434 return -EFAULT;435 436 ret = fw_mgmt_backend_fw_version_operation(fw_mgmt,437 &backend_fw_info);438 if (ret)439 return ret;440 441 if (copy_to_user(buf, &backend_fw_info,442 sizeof(backend_fw_info)))443 return -EFAULT;444 445 return 0;446 case FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE:447 if (copy_from_user(&intf_load, buf, sizeof(intf_load)))448 return -EFAULT;449 450 ret = fw_mgmt_load_and_validate_operation(fw_mgmt,451 intf_load.load_method, intf_load.firmware_tag);452 if (ret)453 return ret;454 455 if (!wait_for_completion_timeout(&fw_mgmt->completion,456 fw_mgmt->timeout_jiffies)) {457 dev_err(fw_mgmt->parent, "timed out waiting for firmware load and validation to finish\n");458 return -ETIMEDOUT;459 }460 461 intf_load.status = fw_mgmt->intf_fw_status;462 intf_load.major = fw_mgmt->intf_fw_major;463 intf_load.minor = fw_mgmt->intf_fw_minor;464 465 if (copy_to_user(buf, &intf_load, sizeof(intf_load)))466 return -EFAULT;467 468 return 0;469 case FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE:470 if (copy_from_user(&backend_update, buf,471 sizeof(backend_update)))472 return -EFAULT;473 474 ret = fw_mgmt_backend_fw_update_operation(fw_mgmt,475 backend_update.firmware_tag);476 if (ret)477 return ret;478 479 if (!wait_for_completion_timeout(&fw_mgmt->completion,480 fw_mgmt->timeout_jiffies)) {481 dev_err(fw_mgmt->parent, "timed out waiting for backend firmware update to finish\n");482 return -ETIMEDOUT;483 }484 485 backend_update.status = fw_mgmt->backend_fw_status;486 487 if (copy_to_user(buf, &backend_update, sizeof(backend_update)))488 return -EFAULT;489 490 return 0;491 case FW_MGMT_IOC_SET_TIMEOUT_MS:492 if (get_user(timeout, (unsigned int __user *)buf))493 return -EFAULT;494 495 if (!timeout) {496 dev_err(fw_mgmt->parent, "timeout can't be zero\n");497 return -EINVAL;498 }499 500 fw_mgmt->timeout_jiffies = msecs_to_jiffies(timeout);501 502 return 0;503 case FW_MGMT_IOC_MODE_SWITCH:504 if (!fw_mgmt->intf_fw_loaded) {505 dev_err(fw_mgmt->parent,506 "Firmware not loaded for mode-switch\n");507 return -EPERM;508 }509 510 /*511 * Disallow new ioctls as the fw-core bundle driver is going to512 * get disconnected soon and the character device will get513 * removed.514 */515 fw_mgmt->mode_switch_started = true;516 517 ret = gb_interface_request_mode_switch(fw_mgmt->connection->intf);518 if (ret) {519 dev_err(fw_mgmt->parent, "Mode-switch failed: %d\n",520 ret);521 fw_mgmt->mode_switch_started = false;522 return ret;523 }524 525 return 0;526 default:527 return -ENOTTY;528 }529}530 531static long fw_mgmt_ioctl_unlocked(struct file *file, unsigned int cmd,532 unsigned long arg)533{534 struct fw_mgmt *fw_mgmt = file->private_data;535 struct gb_bundle *bundle = fw_mgmt->connection->bundle;536 int ret = -ENODEV;537 538 /*539 * Serialize ioctls.540 *541 * We don't want the user to do few operations in parallel. For example,542 * updating Interface firmware in parallel for the same Interface. There543 * is no need to do things in parallel for speed and we can avoid having544 * complicated code for now.545 *546 * This is also used to protect ->disabled, which is used to check if547 * the connection is getting disconnected, so that we don't start any548 * new operations.549 */550 mutex_lock(&fw_mgmt->mutex);551 if (!fw_mgmt->disabled) {552 ret = gb_pm_runtime_get_sync(bundle);553 if (!ret) {554 ret = fw_mgmt_ioctl(fw_mgmt, cmd, (void __user *)arg);555 gb_pm_runtime_put_autosuspend(bundle);556 }557 }558 mutex_unlock(&fw_mgmt->mutex);559 560 return ret;561}562 563static const struct file_operations fw_mgmt_fops = {564 .owner = THIS_MODULE,565 .open = fw_mgmt_open,566 .release = fw_mgmt_release,567 .unlocked_ioctl = fw_mgmt_ioctl_unlocked,568};569 570int gb_fw_mgmt_request_handler(struct gb_operation *op)571{572 u8 type = op->type;573 574 switch (type) {575 case GB_FW_MGMT_TYPE_LOADED_FW:576 return fw_mgmt_interface_fw_loaded_operation(op);577 case GB_FW_MGMT_TYPE_BACKEND_FW_UPDATED:578 return fw_mgmt_backend_fw_updated_operation(op);579 default:580 dev_err(&op->connection->bundle->dev,581 "unsupported request: %u\n", type);582 return -EINVAL;583 }584}585 586int gb_fw_mgmt_connection_init(struct gb_connection *connection)587{588 struct fw_mgmt *fw_mgmt;589 int ret, minor;590 591 if (!connection)592 return 0;593 594 fw_mgmt = kzalloc(sizeof(*fw_mgmt), GFP_KERNEL);595 if (!fw_mgmt)596 return -ENOMEM;597 598 fw_mgmt->parent = &connection->bundle->dev;599 fw_mgmt->timeout_jiffies = msecs_to_jiffies(FW_MGMT_TIMEOUT_MS);600 fw_mgmt->connection = connection;601 602 gb_connection_set_data(connection, fw_mgmt);603 init_completion(&fw_mgmt->completion);604 ida_init(&fw_mgmt->id_map);605 mutex_init(&fw_mgmt->mutex);606 kref_init(&fw_mgmt->kref);607 608 mutex_lock(&list_mutex);609 list_add(&fw_mgmt->node, &fw_mgmt_list);610 mutex_unlock(&list_mutex);611 612 ret = gb_connection_enable(connection);613 if (ret)614 goto err_list_del;615 616 minor = ida_alloc_max(&fw_mgmt_minors_map, NUM_MINORS - 1, GFP_KERNEL);617 if (minor < 0) {618 ret = minor;619 goto err_connection_disable;620 }621 622 /* Add a char device to allow userspace to interact with fw-mgmt */623 fw_mgmt->dev_num = MKDEV(MAJOR(fw_mgmt_dev_num), minor);624 cdev_init(&fw_mgmt->cdev, &fw_mgmt_fops);625 626 ret = cdev_add(&fw_mgmt->cdev, fw_mgmt->dev_num, 1);627 if (ret)628 goto err_remove_ida;629 630 /* Add a soft link to the previously added char-dev within the bundle */631 fw_mgmt->class_device = device_create(&fw_mgmt_class, fw_mgmt->parent,632 fw_mgmt->dev_num, NULL,633 "gb-fw-mgmt-%d", minor);634 if (IS_ERR(fw_mgmt->class_device)) {635 ret = PTR_ERR(fw_mgmt->class_device);636 goto err_del_cdev;637 }638 639 return 0;640 641err_del_cdev:642 cdev_del(&fw_mgmt->cdev);643err_remove_ida:644 ida_free(&fw_mgmt_minors_map, minor);645err_connection_disable:646 gb_connection_disable(connection);647err_list_del:648 mutex_lock(&list_mutex);649 list_del(&fw_mgmt->node);650 mutex_unlock(&list_mutex);651 652 put_fw_mgmt(fw_mgmt);653 654 return ret;655}656 657void gb_fw_mgmt_connection_exit(struct gb_connection *connection)658{659 struct fw_mgmt *fw_mgmt;660 661 if (!connection)662 return;663 664 fw_mgmt = gb_connection_get_data(connection);665 666 device_destroy(&fw_mgmt_class, fw_mgmt->dev_num);667 cdev_del(&fw_mgmt->cdev);668 ida_free(&fw_mgmt_minors_map, MINOR(fw_mgmt->dev_num));669 670 /*671 * Disallow any new ioctl operations on the char device and wait for672 * existing ones to finish.673 */674 mutex_lock(&fw_mgmt->mutex);675 fw_mgmt->disabled = true;676 mutex_unlock(&fw_mgmt->mutex);677 678 /* All pending greybus operations should have finished by now */679 gb_connection_disable(fw_mgmt->connection);680 681 /* Disallow new users to get access to the fw_mgmt structure */682 mutex_lock(&list_mutex);683 list_del(&fw_mgmt->node);684 mutex_unlock(&list_mutex);685 686 /*687 * All current users of fw_mgmt would have taken a reference to it by688 * now, we can drop our reference and wait the last user will get689 * fw_mgmt freed.690 */691 put_fw_mgmt(fw_mgmt);692}693 694int fw_mgmt_init(void)695{696 int ret;697 698 ret = class_register(&fw_mgmt_class);699 if (ret)700 return ret;701 702 ret = alloc_chrdev_region(&fw_mgmt_dev_num, 0, NUM_MINORS,703 "gb_fw_mgmt");704 if (ret)705 goto err_remove_class;706 707 return 0;708 709err_remove_class:710 class_unregister(&fw_mgmt_class);711 return ret;712}713 714void fw_mgmt_exit(void)715{716 unregister_chrdev_region(fw_mgmt_dev_num, NUM_MINORS);717 class_unregister(&fw_mgmt_class);718 ida_destroy(&fw_mgmt_minors_map);719}720