diff options
Diffstat (limited to 'drivers/mtd/devices/powernv_flash.c')
| -rw-r--r-- | drivers/mtd/devices/powernv_flash.c | 121 |
1 files changed, 67 insertions, 54 deletions
diff --git a/drivers/mtd/devices/powernv_flash.c b/drivers/mtd/devices/powernv_flash.c index f5396f26ddb4..a12427d9e20f 100644 --- a/drivers/mtd/devices/powernv_flash.c +++ b/drivers/mtd/devices/powernv_flash.c @@ -1,17 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * OPAL PNOR flash MTD abstraction * * Copyright IBM 2015 - * - * 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. */ #include <linux/kernel.h> @@ -47,6 +38,11 @@ enum flash_op { FLASH_OP_ERASE, }; +/* + * Don't return -ERESTARTSYS if we can't get a token, the MTD core + * might have split up the call from userspace and called into the + * driver more than once, we'll already have done some amount of work. + */ static int powernv_flash_async_op(struct mtd_info *mtd, enum flash_op op, loff_t offset, size_t len, size_t *retlen, u_char *buf) { @@ -63,7 +59,8 @@ static int powernv_flash_async_op(struct mtd_info *mtd, enum flash_op op, if (token < 0) { if (token != -ERESTARTSYS) dev_err(dev, "Failed to get an async token\n"); - + else + token = -EINTR; return token; } @@ -78,36 +75,58 @@ static int powernv_flash_async_op(struct mtd_info *mtd, enum flash_op op, rc = opal_flash_erase(info->id, offset, len, token); break; default: - BUG_ON(1); - } - - if (rc != OPAL_ASYNC_COMPLETION) { - dev_err(dev, "opal_flash_async_op(op=%d) failed (rc %d)\n", - op, rc); + WARN_ON_ONCE(1); opal_async_release_token(token); return -EIO; } - rc = opal_async_wait_response(token, &msg); - opal_async_release_token(token); - if (rc) { - dev_err(dev, "opal async wait failed (rc %d)\n", rc); - return -EIO; + if (rc == OPAL_ASYNC_COMPLETION) { + rc = opal_async_wait_response_interruptible(token, &msg); + if (rc) { + /* + * If we return the mtd core will free the + * buffer we've just passed to OPAL but OPAL + * will continue to read or write from that + * memory. + * It may be tempting to ultimately return 0 + * if we're doing a read or a write since we + * are going to end up waiting until OPAL is + * done. However, because the MTD core sends + * us the userspace request in chunks, we need + * it to know we've been interrupted. + */ + rc = -EINTR; + if (opal_async_wait_response(token, &msg)) + dev_err(dev, "opal_async_wait_response() failed\n"); + goto out; + } + rc = opal_get_async_rc(msg); } - rc = opal_get_async_rc(msg); - if (rc == OPAL_SUCCESS) { - rc = 0; - if (retlen) - *retlen = len; - } else { - rc = -EIO; - } + /* + * OPAL does mutual exclusion on the flash, it will return + * OPAL_BUSY. + * During firmware updates by the service processor OPAL may + * be (temporarily) prevented from accessing the flash, in + * this case OPAL will also return OPAL_BUSY. + * Both cases aren't errors exactly but the flash could have + * changed, userspace should be informed. + */ + if (rc != OPAL_SUCCESS && rc != OPAL_BUSY) + dev_err(dev, "opal_flash_async_op(op=%d) failed (rc %d)\n", + op, rc); + if (rc == OPAL_SUCCESS && retlen) + *retlen = len; + + rc = opal_error_code(rc); +out: + opal_async_release_token(token); return rc; } /** + * powernv_flash_read * @mtd: the device * @from: the offset to read from * @len: the number of bytes to read @@ -124,6 +143,7 @@ static int powernv_flash_read(struct mtd_info *mtd, loff_t from, size_t len, } /** + * powernv_flash_write * @mtd: the device * @to: the offset to write to * @len: the number of bytes to write @@ -140,6 +160,7 @@ static int powernv_flash_write(struct mtd_info *mtd, loff_t to, size_t len, } /** + * powernv_flash_erase * @mtd: the device * @erase: the erase info * Returns 0 if erase successful or -ERRNO if an error occurred @@ -148,25 +169,17 @@ static int powernv_flash_erase(struct mtd_info *mtd, struct erase_info *erase) { int rc; - erase->state = MTD_ERASING; - - /* todo: register our own notifier to do a true async implementation */ rc = powernv_flash_async_op(mtd, FLASH_OP_ERASE, erase->addr, erase->len, NULL, NULL); - - if (rc) { + if (rc) erase->fail_addr = erase->addr; - erase->state = MTD_ERASE_FAILED; - } else { - erase->state = MTD_ERASE_DONE; - } - mtd_erase_callback(erase); + return rc; } /** * powernv_flash_set_driver_info - Fill the mtd_info structure and docg3 - * structure @pdev: The platform device + * @dev: The device structure * @mtd: The structure to fill */ static int powernv_flash_set_driver_info(struct device *dev, @@ -193,7 +206,10 @@ static int powernv_flash_set_driver_info(struct device *dev, * Going to have to check what details I need to set and how to * get them */ - mtd->name = of_get_property(dev->of_node, "name", NULL); + mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%pOFP", dev->of_node); + if (!mtd->name) + return -ENOMEM; + mtd->type = MTD_NORFLASH; mtd->flags = MTD_WRITEABLE; mtd->size = size; @@ -204,6 +220,7 @@ static int powernv_flash_set_driver_info(struct device *dev, mtd->_read = powernv_flash_read; mtd->_write = powernv_flash_write; mtd->dev.parent = dev; + mtd_set_of_node(mtd, dev->of_node); return 0; } @@ -220,21 +237,20 @@ static int powernv_flash_probe(struct platform_device *pdev) int ret; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) { - ret = -ENOMEM; - goto out; - } + if (!data) + return -ENOMEM; + data->mtd.priv = data; ret = of_property_read_u32(dev->of_node, "ibm,opal-id", &(data->id)); if (ret) { dev_err(dev, "no device property 'ibm,opal-id'\n"); - goto out; + return ret; } ret = powernv_flash_set_driver_info(dev, &data->mtd); if (ret) - goto out; + return ret; dev_set_drvdata(dev, data); @@ -243,10 +259,7 @@ static int powernv_flash_probe(struct platform_device *pdev) * with an ffs partition at the start, it should prove easier for users * to deal with partitions or not as they see fit */ - ret = mtd_device_register(&data->mtd, NULL, 0); - -out: - return ret; + return mtd_device_register(&data->mtd, NULL, 0); } /** @@ -255,12 +268,12 @@ out: * * Returns 0 */ -static int powernv_flash_release(struct platform_device *pdev) +static void powernv_flash_release(struct platform_device *pdev) { struct powernv_flash *data = dev_get_drvdata(&(pdev->dev)); /* All resources should be freed automatically */ - return mtd_device_unregister(&(data->mtd)); + WARN_ON(mtd_device_unregister(&data->mtd)); } static const struct of_device_id powernv_flash_match[] = { |
