# --- T2-COPYRIGHT-NOTE-BEGIN --- # T2 SDE: architecture/powerpc64/package/*/0030-ps3flash.patch # Copyright (C) 2019 - 2024 The T2 SDE Project # # This Copyright note is generated by scripts/Create-CopyPatch, # more information can be found in the files COPYING and README. # # This patch file is dual-licensed. It is available under the license the # patched project is licensed under, as long as it is an OpenSource license # as defined at http://www.opensource.org/ (e.g. BSD, X11) or under the terms # of the GNU General Public License version 2 as used by the T2 SDE. # --- T2-COPYRIGHT-NOTE-END --- --- a/arch/powerpc/include/asm/ps3.h +++ b/arch/powerpc/include/asm/ps3.h @@ -313,6 +313,7 @@ enum ps3_match_id { PS3_MATCH_ID_SOUND = 9, PS3_MATCH_ID_GPU = 10, PS3_MATCH_ID_LPM = 11, + PS3_MATCH_ID_STOR_NOR_FLASH = 12, }; enum ps3_match_sub_id { @@ -332,6 +333,7 @@ enum ps3_match_sub_id { #define PS3_MODULE_ALIAS_GPU_FB "ps3:10:1" #define PS3_MODULE_ALIAS_GPU_RAMDISK "ps3:10:2" #define PS3_MODULE_ALIAS_LPM "ps3:11:0" +#define PS3_MODULE_ALIAS_STOR_NOR_FLASH "ps3:12:0" enum ps3_system_bus_device_type { PS3_DEVICE_TYPE_IOC0 = 1, --- a/arch/powerpc/platforms/ps3/platform.h +++ b/arch/powerpc/platforms/ps3/platform.h @@ -76,6 +76,7 @@ enum ps3_dev_type { PS3_DEV_TYPE_STOR_ROM = TYPE_ROM, /* 5 */ PS3_DEV_TYPE_SB_GPIO = 6, PS3_DEV_TYPE_STOR_FLASH = TYPE_RBC, /* 14 */ + PS3_DEV_TYPE_STOR_NOR_FLASH = 254, }; int ps3_repository_read_bus_str(unsigned int bus_index, const char *bus_str, --- a/arch/powerpc/platforms/ps3/device-init.c +++ b/arch/powerpc/platforms/ps3/device-init.c @@ -578,6 +578,13 @@ static int ps3_setup_dynamic_device(const struct ps3_repository_device *repo) __func__, __LINE__); break; + case PS3_DEV_TYPE_STOR_NOR_FLASH: + result = ps3_setup_storage_dev(repo, PS3_MATCH_ID_STOR_NOR_FLASH); + if (result) + pr_debug("%s:%u ps3_setup_storage_dev failed\n", + __func__, __LINE__); + break; + default: result = 0; pr_debug("%s:%u: unsupported dev_type %u\n", __func__, __LINE__, --- a/arch/powerpc/platforms/ps3/system-bus.c +++ b/arch/powerpc/platforms/ps3/system-bus.c @@ -162,6 +162,7 @@ int ps3_open_hv_device(struct ps3_system_bus_device *dev) case PS3_MATCH_ID_STOR_DISK: case PS3_MATCH_ID_STOR_ROM: case PS3_MATCH_ID_STOR_FLASH: + case PS3_MATCH_ID_STOR_NOR_FLASH: return ps3_open_hv_device_sb(dev); case PS3_MATCH_ID_SOUND: @@ -200,6 +201,7 @@ int ps3_close_hv_device(struct ps3_system_bus_device *dev) case PS3_MATCH_ID_STOR_DISK: case PS3_MATCH_ID_STOR_ROM: case PS3_MATCH_ID_STOR_FLASH: + case PS3_MATCH_ID_STOR_NOR_FLASH: return ps3_close_hv_device_sb(dev); case PS3_MATCH_ID_SOUND: --- a/arch/powerpc/platforms/ps3/Kconfig +++ b/arch/powerpc/platforms/ps3/Kconfig @@ -137,6 +137,26 @@ config PS3_FLASH be disabled on the kernel command line using "ps3flash=off", to not allocate this fixed buffer. +config PS3_FLASH_NG + tristate "PS3 Flash Storage Driver" + depends on PPC_PS3 && BLOCK && PS3_FLASH!=y && PS3_FLASH!=m + select PS3_STORAGE + help + Include support for the PS3 Flash Storage. + + This support is required to access the PS3 flash. + In general, all users will say Y or M. + +config PS3_NOR_FLASH + tristate "PS3 NOR Flash Storage Driver" + depends on PPC_PS3 && BLOCK + select PS3_STORAGE + help + Include support for the PS3 NOR Flash Storage. + + This support is required to access the PS3 NOR flash. + In general, all users will say Y or M. + config PS3_VRAM tristate "PS3 Video RAM Storage Driver" depends on FB_PS3=y && BLOCK && m --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_BLK_DEV_SWIM) += swim_mod.o obj-$(CONFIG_BLK_DEV_FD) += floppy.o obj-$(CONFIG_AMIGA_FLOPPY) += amiflop.o obj-$(CONFIG_PS3_DISK) += ps3disk.o +obj-$(CONFIG_PS3_FLASH_NG) += ps3flash.o +obj-$(CONFIG_PS3_NOR_FLASH) += ps3nflash.o obj-$(CONFIG_PS3_VRAM) += ps3vram.o obj-$(CONFIG_ATARI_FLOPPY) += ataflop.o obj-$(CONFIG_AMIGA_Z2RAM) += z2ram.o --- /dev/null 2024-08-16 09:57:50.520498092 +0200 +++ linux-6.10/drivers/block/ps3flash.c 2024-08-18 12:36:06.417692202 +0200 @@ -0,0 +1,510 @@ +/* + * PS3 Flash Storage Driver + * + * Copyright (C) 2020-2024 René Rebe . + * Copyright (C) 2019 Emmanuel Nicolet . + * Copyright (C) 2011-2013 glevand . + * Copyright (C) 2011 graf_chokolo . + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2007 Sony Corp. + * + * 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; version 2 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include +#include + + +#define DEVICE_NAME "ps3flash" + +#define BOUNCE_SIZE (64*1024) + +#define PS3FLASH_MAX_NUM_REGS 8 +#define PS3FLASH_MINORS 16 + + +#define PS3FLASH_NAME "ps3flash%c" + + +struct ps3flash_private { + spinlock_t lock; /* Request queue spinlock */ + unsigned int blocking_factor; + struct request *req; + u64 raw_capacity; + int is_vflash; + struct blk_mq_tag_set tag_set[PS3FLASH_MAX_NUM_REGS]; + struct gendisk *gendisk[PS3FLASH_MAX_NUM_REGS]; + struct request_queue *queue[PS3FLASH_MAX_NUM_REGS]; + int next_queue; +}; + + +#define LV1_STORAGE_ATA_FLUSH_CACHE_EXT (0x31) + + +static int ps3flash_major; + + +static const struct block_device_operations ps3flash_fops = { + .owner = THIS_MODULE, +}; + +static unsigned int region_flags[] = +{ + 0x6, 0x2, 0x4, 0x4, 0x4, 0x0, 0x2, 0x0, +}; +module_param_array(region_flags, uint, NULL, S_IRUGO); +MODULE_PARM_DESC(region_flags, "Region flags"); + + +static void ps3flash_scatter_gather(struct ps3_storage_device *dev, + struct request *req, int gather) +{ + unsigned int offset = 0; + struct req_iterator iter; + struct bio_vec bvec; + size_t size; + + rq_for_each_segment(bvec, req, iter) { + dev_dbg(&dev->sbd.core, + "%s:%u: bio %u segs %u sectors from %llu\n", + __func__, __LINE__, bio_segments(iter.bio), + bio_sectors(iter.bio), iter.bio->bi_iter.bi_sector); + + size = bvec.bv_len; + if (gather) + memcpy_from_bvec(dev->bounce_buf + offset, &bvec); + else + memcpy_to_bvec(&bvec, dev->bounce_buf + offset); + offset += size; + //flush_kernel_dcache_page(bvec.bv_page); + } +} + +static blk_status_t ps3flash_submit_request_sg(struct ps3_storage_device *dev, + struct request *req) +{ + struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + int write = rq_data_dir(req), res; + const char *op = write ? "write" : "read"; + u64 start_sector, sectors; + unsigned int region_idx = MINOR(disk_devt(req->q->disk)) / PS3FLASH_MINORS; + unsigned int region_id = dev->regions[region_idx].id; + unsigned int region_flags = dev->regions[region_idx].flags; + +#ifdef DEBUG + unsigned int n = 0; + struct bio_vec bv; + struct req_iterator iter; + + rq_for_each_segment(bv, req, iter) + n++; + dev_dbg(&dev->sbd.core, + "%s:%u: %s req has %u bvecs for %u sectors\n", + __func__, __LINE__, op, n, blk_rq_sectors(req)); +#endif + + start_sector = blk_rq_pos(req) * priv->blocking_factor; + sectors = blk_rq_sectors(req) * priv->blocking_factor; + dev_dbg(&dev->sbd.core, "%s:%u: %s %llu sectors starting at %llu\n", + __func__, __LINE__, op, sectors, start_sector); + + if (write) { + ps3flash_scatter_gather(dev, req, 1); + + res = lv1_storage_write(dev->sbd.dev_id, region_id, + start_sector, sectors, region_flags, + dev->bounce_lpar, &dev->tag); + } else { + res = lv1_storage_read(dev->sbd.dev_id, region_id, + start_sector, sectors, region_flags, + dev->bounce_lpar, &dev->tag); + } + if (res) { + dev_err(&dev->sbd.core, "%s:%u: %s failed %d\n", __func__, + __LINE__, op, res); + return BLK_STS_IOERR; + } + + priv->req = req; + return BLK_STS_OK; +} + +static blk_status_t ps3flash_submit_flush_request(struct ps3_storage_device *dev, + struct request *req) +{ + struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + u64 res; + + dev_dbg(&dev->sbd.core, "%s:%u: flush request\n", __func__, __LINE__); + + res = lv1_storage_send_device_command(dev->sbd.dev_id, + LV1_STORAGE_ATA_FLUSH_CACHE_EXT, 0, 0, 0, + 0, &dev->tag); + if (res) { + dev_err(&dev->sbd.core, "%s:%u: sync cache failed 0x%llx\n", + __func__, __LINE__, res); + return BLK_STS_IOERR; + } + + priv->req = req; + return BLK_STS_OK; +} + +static blk_status_t ps3flash_do_request(struct ps3_storage_device *dev, + struct request *req) +{ + struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + + dev_dbg(&dev->sbd.core, "%s:%u\n", __func__, __LINE__); + + if (priv->is_vflash && req_op(req) == REQ_OP_FLUSH) { + return ps3flash_submit_flush_request(dev, req); + } else if (req_op(req) == REQ_OP_READ || req_op(req) == REQ_OP_WRITE) { + return ps3flash_submit_request_sg(dev, req); + } else { + blk_dump_rq_flags(req, DEVICE_NAME " bad request"); + return BLK_STS_IOERR; + } +} + +static blk_status_t ps3flash_queue_rq(struct blk_mq_hw_ctx *hctx, + const struct blk_mq_queue_data *bd) +{ + struct request_queue *q = hctx->queue; + struct ps3_storage_device *dev = q->queuedata; + struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + blk_status_t ret; + + spin_lock_irq(&priv->lock); + if (priv->req) { + spin_unlock_irq(&priv->lock); + blk_mq_stop_hw_queue(hctx); + return BLK_STS_DEV_RESOURCE; + } + + blk_mq_start_request(bd->rq); + + ret = ps3flash_do_request(dev, bd->rq); + spin_unlock_irq(&priv->lock); + + return ret; +} + +static irqreturn_t ps3flash_interrupt(int irq, void *data) +{ + struct ps3_storage_device *dev = data; + struct ps3flash_private *priv; + struct request *req; + int res, read, error; + u64 tag, status; + const char *op; + struct request_queue *q; + int old_queue; + + res = lv1_storage_get_async_status(dev->sbd.dev_id, &tag, &status); + + if (tag != dev->tag) + dev_err(&dev->sbd.core, + "%s:%u: tag mismatch, got %llx, expected %llx\n", + __func__, __LINE__, tag, dev->tag); + + if (res) { + dev_err(&dev->sbd.core, "%s:%u: res=%d status=0x%llx\n", + __func__, __LINE__, res, status); + return IRQ_HANDLED; + } + + priv = ps3_system_bus_get_drvdata(&dev->sbd); + req = priv->req; + if (!req) { + dev_dbg(&dev->sbd.core, + "%s:%u non-block layer request completed\n", __func__, + __LINE__); + dev->lv1_status = status; + complete(&dev->done); + return IRQ_HANDLED; + } + + if (req_op(req) == REQ_OP_FLUSH) { + read = 0; + op = "flush"; + } else { + read = !rq_data_dir(req); + op = read ? "read" : "write"; + } + if (status) { + dev_dbg(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__, + __LINE__, op, status); + error = -EIO; + } else { + dev_dbg(&dev->sbd.core, "%s:%u: %s completed\n", __func__, + __LINE__, op); + error = 0; + if (read) + ps3flash_scatter_gather(dev, req, 0); + } + + spin_lock(&priv->lock); + priv->req = NULL; + blk_mq_end_request(req, error); + old_queue = priv->next_queue; + do { + q = priv->queue[priv->next_queue]; + + priv->next_queue++; + if (priv->next_queue >= dev->num_regions) + priv->next_queue = 0; + + if (q && blk_mq_queue_stopped(q)) { + blk_mq_start_stopped_hw_queues(q, true); + break; + } + } while (old_queue != priv->next_queue); + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static int ps3flash_sync_cache(struct ps3_storage_device *dev) +{ + u64 res; + + dev_dbg(&dev->sbd.core, "%s:%u: sync cache\n", __func__, __LINE__); + + res = ps3stor_send_command(dev, LV1_STORAGE_ATA_FLUSH_CACHE_EXT, 0, 0, 0, 0); + if (res) { + dev_err(&dev->sbd.core, "%s:%u: sync cache failed 0x%llx\n", + __func__, __LINE__, res); + return -EIO; + } + return 0; +} + +static const struct blk_mq_ops ps3flash_mq_ops = { + .queue_rq = ps3flash_queue_rq, +}; + +static int ps3flash_probe(struct ps3_system_bus_device *_dev) +{ + struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core); + struct ps3flash_private *priv; + int error; + unsigned int regidx, devidx; + struct queue_limits lim = { + .logical_block_size = dev->blk_size, + .max_hw_sectors = dev->bounce_size >> 9, + .max_segments = -1, + .max_segment_size = dev->bounce_size, + .dma_alignment = dev->blk_size - 1, + }; + u64 lpar_id, flash_ext_flag, junk; + struct request_queue *queue; + struct gendisk *gendisk; + + BUG_ON(dev->num_regions > PS3FLASH_MAX_NUM_REGS); + + if (dev->blk_size < 512) { + dev_err(&dev->sbd.core, + "%s:%u: cannot handle block size %llu\n", __func__, + __LINE__, dev->blk_size); + return -EINVAL; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + error = -ENOMEM; + goto fail; + } + + ps3_system_bus_set_drvdata(_dev, priv); + spin_lock_init(&priv->lock); + + dev->bounce_size = BOUNCE_SIZE; + dev->bounce_buf = kmalloc(BOUNCE_SIZE, GFP_DMA); + if (!dev->bounce_buf) { + error = -ENOMEM; + goto fail_free_priv; + } + + for (regidx = 0; regidx < dev->num_regions; regidx++) + dev->regions[regidx].flags = region_flags[regidx]; + + error = ps3stor_setup(dev, ps3flash_interrupt); + if (error) + goto fail_free_bounce; + + priv->raw_capacity = dev->regions[0].size; + + error = lv1_get_logical_partition_id(&lpar_id); + if (error) + goto fail_teardown; + + error = lv1_read_repository_node(1, 0x0000000073797300ul /* sys */, + 0x666c617368000000ul /* flash */, + 0x6578740000000000ul /* ext */, + 0, &flash_ext_flag, &junk); + if (error) + goto fail_teardown; + + priv->is_vflash = !(flash_ext_flag & 0x1); + + dev_info(&dev->sbd.core, "VFLASH is %s\n", + priv->is_vflash ? "on" : "off"); + + for (devidx = 0; devidx < dev->num_regions; devidx++) { + if (test_bit(devidx, &dev->accessible_regions) == 0) + continue; + + error = blk_mq_alloc_sq_tag_set(&priv->tag_set[devidx], &ps3flash_mq_ops, 1, + BLK_MQ_F_SHOULD_MERGE); + if (error) { + dev_err(&dev->sbd.core, "%s:%u: blk_mq_alloc_sq_tag_set failed\n", + __func__, __LINE__); + goto fail_cleanup; + } + + gendisk = blk_mq_alloc_disk(&priv->tag_set[devidx], &lim, dev); + if (IS_ERR(gendisk)) { + dev_err(&dev->sbd.core, "%s:%u: blk_mq_alloc_disk failed\n", + __func__, __LINE__); + error = PTR_ERR(gendisk); + goto fail_cleanup; + } + + queue = gendisk->queue; + blk_queue_write_cache(queue, true, false); + + priv->queue[devidx] = queue; + priv->gendisk[devidx] = gendisk; + + gendisk->major = ps3flash_major; + gendisk->first_minor = devidx * PS3FLASH_MINORS; + gendisk->fops = &ps3flash_fops; + gendisk->private_data = dev; + snprintf(gendisk->disk_name, sizeof(gendisk->disk_name), PS3FLASH_NAME, + devidx+'a'); + priv->blocking_factor = dev->blk_size >> 9; + set_capacity(gendisk, + dev->regions[devidx].size*priv->blocking_factor); + + dev_info(&dev->sbd.core, + "%s (%llu MiB total, %llu MiB region)\n", + gendisk->disk_name, priv->raw_capacity >> 11, + get_capacity(gendisk) >> 11); + + error = device_add_disk(&dev->sbd.core, gendisk, NULL); + if (error) + goto fail_cleanup; + } + + return 0; + +fail_cleanup: + for (devidx = 0; devidx < dev->num_regions; devidx++) { + if (priv->gendisk[devidx]) { + del_gendisk(priv->gendisk[devidx]); + put_disk(priv->gendisk[devidx]); + blk_mq_free_tag_set(&priv->tag_set[devidx]); + } + } +fail_teardown: + ps3stor_teardown(dev); +fail_free_bounce: + kfree(dev->bounce_buf); +fail_free_priv: + kfree(priv); + ps3_system_bus_set_drvdata(_dev, NULL); +fail: + return error; +} + +static void ps3flash_remove(struct ps3_system_bus_device *_dev) +{ + struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core); + struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + unsigned int devidx; + + for (devidx = 0; devidx < dev->num_regions; devidx++) { + if (priv->gendisk[devidx]) { + del_gendisk(priv->gendisk[devidx]); + put_disk(priv->gendisk[devidx]); + blk_mq_free_tag_set(&priv->tag_set[devidx]); + } + } + + if (priv->is_vflash) { + dev_notice(&dev->sbd.core, "Synchronizing disk cache\n"); + ps3flash_sync_cache(dev); + } + + ps3stor_teardown(dev); + kfree(dev->bounce_buf); + kfree(priv); + ps3_system_bus_set_drvdata(_dev, NULL); +} + +static struct ps3_system_bus_driver ps3flash = { + .match_id = PS3_MATCH_ID_STOR_FLASH, + .core.name = DEVICE_NAME, + .core.owner = THIS_MODULE, + .probe = ps3flash_probe, + .remove = ps3flash_remove, + .shutdown = ps3flash_remove, +}; + + +static int __init ps3flash_init(void) +{ + int error; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENODEV; + + error = register_blkdev(0, DEVICE_NAME); + if (error <= 0) { + printk(KERN_ERR "%s:%u: register_blkdev failed %d\n", __func__, + __LINE__, error); + return error; + } + ps3flash_major = error; + + pr_info("%s:%u: registered block device major %d\n", __func__, + __LINE__, ps3flash_major); + + error = ps3_system_bus_driver_register(&ps3flash); + if (error) + unregister_blkdev(ps3flash_major, DEVICE_NAME); + + return error; +} + +static void __exit ps3flash_exit(void) +{ + ps3_system_bus_driver_unregister(&ps3flash); + unregister_blkdev(ps3flash_major, DEVICE_NAME); +} + +module_init(ps3flash_init); +module_exit(ps3flash_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PS3 Flash Storage Driver"); +MODULE_AUTHOR("Sony Corporation"); +MODULE_ALIAS(PS3_MODULE_ALIAS_STOR_FLASH); --- /dev/null 2024-08-16 09:57:50.520498092 +0200 +++ linux-6.10/drivers/block/ps3nflash.c 2024-08-18 12:36:15.668693225 +0200 @@ -0,0 +1,439 @@ +/* + * PS3 Flash Storage Driver + * + * Copyright (C) 2020-2024 René Rebe . + * Copyright (C) 2019 Emmanuel Nicolet . + * Copyright (C) 2011-2013 glevand . + * Copyright (C) 2011 graf_chokolo . + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2007 Sony Corp. + * + * 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; version 2 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include +#include + + +#define DEVICE_NAME "ps3nflash" + +#define BOUNCE_SIZE (64*1024) +#define PS3NFLASH_MAX_NUM_REGS 8 +#define PS3NFLASH_MINORS 16 + +#define PS3NFLASH_NAME "ps3nflash%c" + + +struct ps3nflash_private { + spinlock_t lock; /* Request queue spinlock */ + unsigned int blocking_factor; + struct request *req; + u64 raw_capacity; + struct blk_mq_tag_set tag_set[PS3NFLASH_MAX_NUM_REGS]; + struct gendisk *gendisk[PS3NFLASH_MAX_NUM_REGS]; + struct request_queue *queue[PS3NFLASH_MAX_NUM_REGS]; + int next_queue; +}; + + +static int ps3nflash_major; + + +static const struct block_device_operations ps3nflash_fops = { + .owner = THIS_MODULE, +}; + +static unsigned int region_flags[] = +{ + 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, +}; +module_param_array(region_flags, uint, NULL, S_IRUGO); +MODULE_PARM_DESC(region_flags, "Region flags"); + + +static void ps3nflash_scatter_gather(struct ps3_storage_device *dev, + struct request *req, int gather) +{ + unsigned int offset = 0; + struct req_iterator iter; + struct bio_vec bvec; + size_t size; + + rq_for_each_segment(bvec, req, iter) { + dev_dbg(&dev->sbd.core, + "%s:%u: bio %u segs %u sectors from %llu\n", + __func__, __LINE__, bio_segments(iter.bio), + bio_sectors(iter.bio), iter.bio->bi_iter.bi_sector); + + size = bvec.bv_len; + if (gather) + memcpy_from_bvec(dev->bounce_buf + offset, &bvec); + else + memcpy_to_bvec(&bvec, dev->bounce_buf + offset); + offset += size; + //flush_kernel_dcache_page(bvec.bv_page); + } +} + +static blk_status_t ps3nflash_submit_request_sg(struct ps3_storage_device *dev, + struct request *req) +{ + struct ps3nflash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + int write = rq_data_dir(req), res; + const char *op = write ? "write" : "read"; + u64 start_sector, sectors; + unsigned int region_idx = MINOR(disk_devt(req->q->disk)) / PS3NFLASH_MINORS; + unsigned int region_id = dev->regions[region_idx].id; + unsigned int region_flags = dev->regions[region_idx].flags; + +#ifdef DEBUG + unsigned int n = 0; + struct bio_vec bv; + struct req_iterator iter; + + rq_for_each_segment(bv, req, iter) + n++; + dev_dbg(&dev->sbd.core, + "%s:%u: %s req has %u bvecs for %u sectors\n", + __func__, __LINE__, op, n, blk_rq_sectors(req)); +#endif + + start_sector = blk_rq_pos(req) * priv->blocking_factor; + sectors = blk_rq_sectors(req) * priv->blocking_factor; + dev_dbg(&dev->sbd.core, "%s:%u: %s %llu sectors starting at %llu\n", + __func__, __LINE__, op, sectors, start_sector); + + if (write) { + ps3nflash_scatter_gather(dev, req, 1); + + res = lv1_storage_write(dev->sbd.dev_id, region_id, + start_sector, sectors, region_flags, + dev->bounce_lpar, &dev->tag); + } else { + res = lv1_storage_read(dev->sbd.dev_id, region_id, + start_sector, sectors, region_flags, + dev->bounce_lpar, &dev->tag); + } + if (res) { + dev_err(&dev->sbd.core, "%s:%u: %s failed %d\n", __func__, + __LINE__, op, res); + return BLK_STS_IOERR; + } + + priv->req = req; + return BLK_STS_OK; +} + +static blk_status_t ps3nflash_do_request(struct ps3_storage_device *dev, + struct request *req) +{ + dev_dbg(&dev->sbd.core, "%s:%u\n", __func__, __LINE__); + + switch (req_op(req)) { + case REQ_OP_READ: + case REQ_OP_WRITE: + case REQ_OP_FLUSH: + return ps3nflash_submit_request_sg(dev, req); + default: + blk_dump_rq_flags(req, DEVICE_NAME " bad request"); + return BLK_STS_IOERR; + } +} + +static blk_status_t ps3nflash_queue_rq(struct blk_mq_hw_ctx *hctx, + const struct blk_mq_queue_data *bd) +{ + struct request_queue *q = hctx->queue; + struct ps3_storage_device *dev = q->queuedata; + struct ps3nflash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + blk_status_t ret; + + spin_lock_irq(&priv->lock); + if (priv->req) { + spin_unlock_irq(&priv->lock); + blk_mq_stop_hw_queue(hctx); + return BLK_STS_DEV_RESOURCE; + } + + blk_mq_start_request(bd->rq); + + ret = ps3nflash_do_request(dev, bd->rq); + spin_unlock_irq(&priv->lock); + + return ret; +} + +static irqreturn_t ps3nflash_interrupt(int irq, void *data) +{ + struct ps3_storage_device *dev = data; + struct ps3nflash_private *priv; + struct request *req; + int res, read, error; + u64 tag, status; + const char *op; + struct request_queue *q; + int old_queue; + + res = lv1_storage_get_async_status(dev->sbd.dev_id, &tag, &status); + + if (tag != dev->tag) + dev_err(&dev->sbd.core, + "%s:%u: tag mismatch, got %llx, expected %llx\n", + __func__, __LINE__, tag, dev->tag); + + if (res) { + dev_err(&dev->sbd.core, "%s:%u: res=%d status=0x%llx\n", + __func__, __LINE__, res, status); + return IRQ_HANDLED; + } + + priv = ps3_system_bus_get_drvdata(&dev->sbd); + req = priv->req; + if (!req) { + dev_dbg(&dev->sbd.core, + "%s:%u non-block layer request completed\n", __func__, + __LINE__); + dev->lv1_status = status; + complete(&dev->done); + return IRQ_HANDLED; + } + + read = !rq_data_dir(req); + op = read ? "read" : "write"; + + if (status) { + dev_dbg(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__, + __LINE__, op, status); + error = -EIO; + } else { + dev_dbg(&dev->sbd.core, "%s:%u: %s completed\n", __func__, + __LINE__, op); + error = 0; + if (read) + ps3nflash_scatter_gather(dev, req, 0); + } + + spin_lock(&priv->lock); + priv->req = NULL; + blk_mq_end_request(req, error); + old_queue = priv->next_queue; + do { + q = priv->queue[priv->next_queue]; + + priv->next_queue++; + if (priv->next_queue >= dev->num_regions) + priv->next_queue = 0; + + if (q && blk_mq_queue_stopped(q)) { + blk_mq_start_stopped_hw_queues(q, true); + break; + } + } while (old_queue != priv->next_queue); + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static const struct blk_mq_ops ps3nflash_mq_ops = { + .queue_rq = ps3nflash_queue_rq, +}; + +static int ps3nflash_probe(struct ps3_system_bus_device *_dev) +{ + struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core); + struct ps3nflash_private *priv; + int error; + unsigned int regidx, devidx; + struct queue_limits lim = { + .logical_block_size = dev->blk_size, + .max_hw_sectors = dev->bounce_size >> 9, + .max_segments = -1, + .max_segment_size = dev->bounce_size, + .dma_alignment = dev->blk_size - 1, + }; + struct request_queue *queue; + struct gendisk *gendisk; + + BUG_ON(dev->num_regions > PS3NFLASH_MAX_NUM_REGS); + + if (dev->blk_size < 512) { + dev_err(&dev->sbd.core, + "%s:%u: cannot handle block size %llu\n", __func__, + __LINE__, dev->blk_size); + return -EINVAL; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + error = -ENOMEM; + goto fail; + } + + ps3_system_bus_set_drvdata(_dev, priv); + spin_lock_init(&priv->lock); + + dev->bounce_size = BOUNCE_SIZE; + dev->bounce_buf = kmalloc(BOUNCE_SIZE, GFP_DMA); + if (!dev->bounce_buf) { + error = -ENOMEM; + goto fail_free_priv; + } + + for (regidx = 0; regidx < dev->num_regions; regidx++) + dev->regions[regidx].flags = region_flags[regidx]; + + error = ps3stor_setup(dev, ps3nflash_interrupt); + if (error) + goto fail_free_bounce; + + priv->raw_capacity = dev->regions[0].size; + + for (devidx = 0; devidx < dev->num_regions; devidx++) { + if (test_bit(devidx, &dev->accessible_regions) == 0) + continue; + + error = blk_mq_alloc_sq_tag_set(&priv->tag_set[devidx], &ps3nflash_mq_ops, 1, + BLK_MQ_F_SHOULD_MERGE); + if (error) { + goto fail_cleanup; + } + + gendisk = blk_mq_alloc_disk(&priv->tag_set[devidx], &lim, dev); + if (IS_ERR(gendisk)) { + dev_err(&dev->sbd.core, "%s:%u: blk_mq_alloc_disk failed\n", + __func__, __LINE__); + error = PTR_ERR(gendisk); + goto fail_cleanup; + } + + queue = gendisk->queue; + blk_queue_write_cache(queue, true, false); + + priv->queue[devidx] = queue; + priv->gendisk[devidx] = gendisk; + + gendisk->major = ps3nflash_major; + gendisk->minors = PS3NFLASH_MINORS; + gendisk->first_minor = devidx * PS3NFLASH_MINORS; + gendisk->fops = &ps3nflash_fops; + gendisk->private_data = dev; + snprintf(gendisk->disk_name, sizeof(gendisk->disk_name), PS3NFLASH_NAME, + devidx + 'a'); + priv->blocking_factor = dev->blk_size >> 9; + set_capacity(gendisk, + dev->regions[devidx].size*priv->blocking_factor); + + dev_info(&dev->sbd.core, + "%s (%llu MiB total, %llu MiB region)\n", + gendisk->disk_name, priv->raw_capacity >> 11, + get_capacity(gendisk) >> 11); + + error = device_add_disk(&dev->sbd.core, gendisk, NULL); + if (error) + goto fail_cleanup; + } + + return 0; + +fail_cleanup: + for (devidx = 0; devidx < dev->num_regions; devidx++) { + if (priv->gendisk[devidx]) { + del_gendisk(priv->gendisk[devidx]); + put_disk(priv->gendisk[devidx]); + blk_mq_free_tag_set(&priv->tag_set[devidx]); + } + } + ps3stor_teardown(dev); +fail_free_bounce: + kfree(dev->bounce_buf); +fail_free_priv: + kfree(priv); + ps3_system_bus_set_drvdata(_dev, NULL); +fail: + return error; +} + +static void ps3nflash_remove(struct ps3_system_bus_device *_dev) +{ + struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core); + struct ps3nflash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); + unsigned int devidx; + + for (devidx = 0; devidx < dev->num_regions; devidx++) { + if (priv->gendisk[devidx]) { + del_gendisk(priv->gendisk[devidx]); + put_disk(priv->gendisk[devidx]); + blk_mq_free_tag_set(&priv->tag_set[devidx]); + } + } + + ps3stor_teardown(dev); + kfree(dev->bounce_buf); + kfree(priv); + ps3_system_bus_set_drvdata(_dev, NULL); +} + +static struct ps3_system_bus_driver ps3nflash = { + .match_id = PS3_MATCH_ID_STOR_NOR_FLASH, + .core.name = DEVICE_NAME, + .core.owner = THIS_MODULE, + .probe = ps3nflash_probe, + .remove = ps3nflash_remove, + .shutdown = ps3nflash_remove, +}; + + +static int __init ps3nflash_init(void) +{ + int error; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENODEV; + + error = register_blkdev(0, DEVICE_NAME); + if (error <= 0) { + printk(KERN_ERR "%s:%u: register_blkdev failed %d\n", __func__, + __LINE__, error); + return error; + } + ps3nflash_major = error; + + pr_info("%s:%u: registered block device major %d\n", __func__, + __LINE__, ps3nflash_major); + + error = ps3_system_bus_driver_register(&ps3nflash); + if (error) + unregister_blkdev(ps3nflash_major, DEVICE_NAME); + + return error; +} + +static void __exit ps3nflash_exit(void) +{ + ps3_system_bus_driver_unregister(&ps3nflash); + unregister_blkdev(ps3nflash_major, DEVICE_NAME); +} + +module_init(ps3nflash_init); +module_exit(ps3nflash_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PS3 NOR Flash Storage Driver"); +MODULE_AUTHOR("Sony Corporation"); +MODULE_ALIAS(PS3_MODULE_ALIAS_STOR_NOR_FLASH);