162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PS3 Storage Library 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2007 Sony Computer Entertainment Inc. 662306a36Sopenharmony_ci * Copyright 2007 Sony Corp. 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <asm/lv1call.h> 1362306a36Sopenharmony_ci#include <asm/ps3stor.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci/* 1662306a36Sopenharmony_ci * A workaround for flash memory I/O errors when the internal hard disk 1762306a36Sopenharmony_ci * has not been formatted for OtherOS use. Delay disk close until flash 1862306a36Sopenharmony_ci * memory is closed. 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic struct ps3_flash_workaround { 2262306a36Sopenharmony_ci int flash_open; 2362306a36Sopenharmony_ci int disk_open; 2462306a36Sopenharmony_ci struct ps3_system_bus_device *disk_sbd; 2562306a36Sopenharmony_ci} ps3_flash_workaround; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic int ps3stor_open_hv_device(struct ps3_system_bus_device *sbd) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci int error = ps3_open_hv_device(sbd); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci if (error) 3262306a36Sopenharmony_ci return error; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci if (sbd->match_id == PS3_MATCH_ID_STOR_FLASH) 3562306a36Sopenharmony_ci ps3_flash_workaround.flash_open = 1; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci if (sbd->match_id == PS3_MATCH_ID_STOR_DISK) 3862306a36Sopenharmony_ci ps3_flash_workaround.disk_open = 1; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci return 0; 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic int ps3stor_close_hv_device(struct ps3_system_bus_device *sbd) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci int error; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (sbd->match_id == PS3_MATCH_ID_STOR_DISK 4862306a36Sopenharmony_ci && ps3_flash_workaround.disk_open 4962306a36Sopenharmony_ci && ps3_flash_workaround.flash_open) { 5062306a36Sopenharmony_ci ps3_flash_workaround.disk_sbd = sbd; 5162306a36Sopenharmony_ci return 0; 5262306a36Sopenharmony_ci } 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci error = ps3_close_hv_device(sbd); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci if (error) 5762306a36Sopenharmony_ci return error; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (sbd->match_id == PS3_MATCH_ID_STOR_DISK) 6062306a36Sopenharmony_ci ps3_flash_workaround.disk_open = 0; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci if (sbd->match_id == PS3_MATCH_ID_STOR_FLASH) { 6362306a36Sopenharmony_ci ps3_flash_workaround.flash_open = 0; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci if (ps3_flash_workaround.disk_sbd) { 6662306a36Sopenharmony_ci ps3_close_hv_device(ps3_flash_workaround.disk_sbd); 6762306a36Sopenharmony_ci ps3_flash_workaround.disk_open = 0; 6862306a36Sopenharmony_ci ps3_flash_workaround.disk_sbd = NULL; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci return 0; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic int ps3stor_probe_access(struct ps3_storage_device *dev) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci int res, error; 7862306a36Sopenharmony_ci unsigned int i; 7962306a36Sopenharmony_ci unsigned long n; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (dev->sbd.match_id == PS3_MATCH_ID_STOR_ROM) { 8262306a36Sopenharmony_ci /* special case: CD-ROM is assumed always accessible */ 8362306a36Sopenharmony_ci dev->accessible_regions = 1; 8462306a36Sopenharmony_ci return 0; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci error = -EPERM; 8862306a36Sopenharmony_ci for (i = 0; i < dev->num_regions; i++) { 8962306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, 9062306a36Sopenharmony_ci "%s:%u: checking accessibility of region %u\n", 9162306a36Sopenharmony_ci __func__, __LINE__, i); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci dev->region_idx = i; 9462306a36Sopenharmony_ci res = ps3stor_read_write_sectors(dev, dev->bounce_lpar, 0, 1, 9562306a36Sopenharmony_ci 0); 9662306a36Sopenharmony_ci if (res) { 9762306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: read failed, " 9862306a36Sopenharmony_ci "region %u is not accessible\n", __func__, 9962306a36Sopenharmony_ci __LINE__, i); 10062306a36Sopenharmony_ci continue; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: region %u is accessible\n", 10462306a36Sopenharmony_ci __func__, __LINE__, i); 10562306a36Sopenharmony_ci set_bit(i, &dev->accessible_regions); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* We can access at least one region */ 10862306a36Sopenharmony_ci error = 0; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci if (error) 11162306a36Sopenharmony_ci return error; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci n = hweight_long(dev->accessible_regions); 11462306a36Sopenharmony_ci if (n > 1) 11562306a36Sopenharmony_ci dev_info(&dev->sbd.core, 11662306a36Sopenharmony_ci "%s:%u: %lu accessible regions found. Only the first " 11762306a36Sopenharmony_ci "one will be used\n", 11862306a36Sopenharmony_ci __func__, __LINE__, n); 11962306a36Sopenharmony_ci dev->region_idx = __ffs(dev->accessible_regions); 12062306a36Sopenharmony_ci dev_info(&dev->sbd.core, 12162306a36Sopenharmony_ci "First accessible region has index %u start %llu size %llu\n", 12262306a36Sopenharmony_ci dev->region_idx, dev->regions[dev->region_idx].start, 12362306a36Sopenharmony_ci dev->regions[dev->region_idx].size); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/** 13062306a36Sopenharmony_ci * ps3stor_setup - Setup a storage device before use 13162306a36Sopenharmony_ci * @dev: Pointer to a struct ps3_storage_device 13262306a36Sopenharmony_ci * @handler: Pointer to an interrupt handler 13362306a36Sopenharmony_ci * 13462306a36Sopenharmony_ci * Returns 0 for success, or an error code 13562306a36Sopenharmony_ci */ 13662306a36Sopenharmony_ciint ps3stor_setup(struct ps3_storage_device *dev, irq_handler_t handler) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci int error, res, alignment; 13962306a36Sopenharmony_ci enum ps3_dma_page_size page_size; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci error = ps3stor_open_hv_device(&dev->sbd); 14262306a36Sopenharmony_ci if (error) { 14362306a36Sopenharmony_ci dev_err(&dev->sbd.core, 14462306a36Sopenharmony_ci "%s:%u: ps3_open_hv_device failed %d\n", __func__, 14562306a36Sopenharmony_ci __LINE__, error); 14662306a36Sopenharmony_ci goto fail; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci error = ps3_sb_event_receive_port_setup(&dev->sbd, PS3_BINDING_CPU_ANY, 15062306a36Sopenharmony_ci &dev->irq); 15162306a36Sopenharmony_ci if (error) { 15262306a36Sopenharmony_ci dev_err(&dev->sbd.core, 15362306a36Sopenharmony_ci "%s:%u: ps3_sb_event_receive_port_setup failed %d\n", 15462306a36Sopenharmony_ci __func__, __LINE__, error); 15562306a36Sopenharmony_ci goto fail_close_device; 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci error = request_irq(dev->irq, handler, 0, 15962306a36Sopenharmony_ci dev->sbd.core.driver->name, dev); 16062306a36Sopenharmony_ci if (error) { 16162306a36Sopenharmony_ci dev_err(&dev->sbd.core, "%s:%u: request_irq failed %d\n", 16262306a36Sopenharmony_ci __func__, __LINE__, error); 16362306a36Sopenharmony_ci goto fail_sb_event_receive_port_destroy; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci alignment = min(__ffs(dev->bounce_size), 16762306a36Sopenharmony_ci __ffs((unsigned long)dev->bounce_buf)); 16862306a36Sopenharmony_ci if (alignment < 12) { 16962306a36Sopenharmony_ci dev_err(&dev->sbd.core, 17062306a36Sopenharmony_ci "%s:%u: bounce buffer not aligned (%lx at 0x%p)\n", 17162306a36Sopenharmony_ci __func__, __LINE__, dev->bounce_size, dev->bounce_buf); 17262306a36Sopenharmony_ci error = -EINVAL; 17362306a36Sopenharmony_ci goto fail_free_irq; 17462306a36Sopenharmony_ci } else if (alignment < 16) 17562306a36Sopenharmony_ci page_size = PS3_DMA_4K; 17662306a36Sopenharmony_ci else 17762306a36Sopenharmony_ci page_size = PS3_DMA_64K; 17862306a36Sopenharmony_ci dev->sbd.d_region = &dev->dma_region; 17962306a36Sopenharmony_ci ps3_dma_region_init(&dev->sbd, &dev->dma_region, page_size, 18062306a36Sopenharmony_ci PS3_DMA_OTHER, dev->bounce_buf, dev->bounce_size); 18162306a36Sopenharmony_ci res = ps3_dma_region_create(&dev->dma_region); 18262306a36Sopenharmony_ci if (res) { 18362306a36Sopenharmony_ci dev_err(&dev->sbd.core, "%s:%u: cannot create DMA region\n", 18462306a36Sopenharmony_ci __func__, __LINE__); 18562306a36Sopenharmony_ci error = -ENOMEM; 18662306a36Sopenharmony_ci goto fail_free_irq; 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci dev->bounce_lpar = ps3_mm_phys_to_lpar(__pa(dev->bounce_buf)); 19062306a36Sopenharmony_ci dev->bounce_dma = dma_map_single(&dev->sbd.core, dev->bounce_buf, 19162306a36Sopenharmony_ci dev->bounce_size, DMA_BIDIRECTIONAL); 19262306a36Sopenharmony_ci if (dma_mapping_error(&dev->sbd.core, dev->bounce_dma)) { 19362306a36Sopenharmony_ci dev_err(&dev->sbd.core, "%s:%u: map DMA region failed\n", 19462306a36Sopenharmony_ci __func__, __LINE__); 19562306a36Sopenharmony_ci error = -ENODEV; 19662306a36Sopenharmony_ci goto fail_free_dma; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci error = ps3stor_probe_access(dev); 20062306a36Sopenharmony_ci if (error) { 20162306a36Sopenharmony_ci dev_err(&dev->sbd.core, "%s:%u: No accessible regions found\n", 20262306a36Sopenharmony_ci __func__, __LINE__); 20362306a36Sopenharmony_ci goto fail_unmap_dma; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci return 0; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cifail_unmap_dma: 20862306a36Sopenharmony_ci dma_unmap_single(&dev->sbd.core, dev->bounce_dma, dev->bounce_size, 20962306a36Sopenharmony_ci DMA_BIDIRECTIONAL); 21062306a36Sopenharmony_cifail_free_dma: 21162306a36Sopenharmony_ci ps3_dma_region_free(&dev->dma_region); 21262306a36Sopenharmony_cifail_free_irq: 21362306a36Sopenharmony_ci free_irq(dev->irq, dev); 21462306a36Sopenharmony_cifail_sb_event_receive_port_destroy: 21562306a36Sopenharmony_ci ps3_sb_event_receive_port_destroy(&dev->sbd, dev->irq); 21662306a36Sopenharmony_cifail_close_device: 21762306a36Sopenharmony_ci ps3stor_close_hv_device(&dev->sbd); 21862306a36Sopenharmony_cifail: 21962306a36Sopenharmony_ci return error; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ps3stor_setup); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci/** 22562306a36Sopenharmony_ci * ps3stor_teardown - Tear down a storage device after use 22662306a36Sopenharmony_ci * @dev: Pointer to a struct ps3_storage_device 22762306a36Sopenharmony_ci */ 22862306a36Sopenharmony_civoid ps3stor_teardown(struct ps3_storage_device *dev) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci int error; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci dma_unmap_single(&dev->sbd.core, dev->bounce_dma, dev->bounce_size, 23362306a36Sopenharmony_ci DMA_BIDIRECTIONAL); 23462306a36Sopenharmony_ci ps3_dma_region_free(&dev->dma_region); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci free_irq(dev->irq, dev); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci error = ps3_sb_event_receive_port_destroy(&dev->sbd, dev->irq); 23962306a36Sopenharmony_ci if (error) 24062306a36Sopenharmony_ci dev_err(&dev->sbd.core, 24162306a36Sopenharmony_ci "%s:%u: destroy event receive port failed %d\n", 24262306a36Sopenharmony_ci __func__, __LINE__, error); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci error = ps3stor_close_hv_device(&dev->sbd); 24562306a36Sopenharmony_ci if (error) 24662306a36Sopenharmony_ci dev_err(&dev->sbd.core, 24762306a36Sopenharmony_ci "%s:%u: ps3_close_hv_device failed %d\n", __func__, 24862306a36Sopenharmony_ci __LINE__, error); 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ps3stor_teardown); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci/** 25462306a36Sopenharmony_ci * ps3stor_read_write_sectors - read/write from/to a storage device 25562306a36Sopenharmony_ci * @dev: Pointer to a struct ps3_storage_device 25662306a36Sopenharmony_ci * @lpar: HV logical partition address 25762306a36Sopenharmony_ci * @start_sector: First sector to read/write 25862306a36Sopenharmony_ci * @sectors: Number of sectors to read/write 25962306a36Sopenharmony_ci * @write: Flag indicating write (non-zero) or read (zero) 26062306a36Sopenharmony_ci * 26162306a36Sopenharmony_ci * Returns 0 for success, -1 in case of failure to submit the command, or 26262306a36Sopenharmony_ci * an LV1 status value in case of other errors 26362306a36Sopenharmony_ci */ 26462306a36Sopenharmony_ciu64 ps3stor_read_write_sectors(struct ps3_storage_device *dev, u64 lpar, 26562306a36Sopenharmony_ci u64 start_sector, u64 sectors, int write) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci unsigned int region_id = dev->regions[dev->region_idx].id; 26862306a36Sopenharmony_ci const char *op = write ? "write" : "read"; 26962306a36Sopenharmony_ci int res; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: %s %llu sectors starting at %llu\n", 27262306a36Sopenharmony_ci __func__, __LINE__, op, sectors, start_sector); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci init_completion(&dev->done); 27562306a36Sopenharmony_ci res = write ? lv1_storage_write(dev->sbd.dev_id, region_id, 27662306a36Sopenharmony_ci start_sector, sectors, 0, lpar, 27762306a36Sopenharmony_ci &dev->tag) 27862306a36Sopenharmony_ci : lv1_storage_read(dev->sbd.dev_id, region_id, 27962306a36Sopenharmony_ci start_sector, sectors, 0, lpar, 28062306a36Sopenharmony_ci &dev->tag); 28162306a36Sopenharmony_ci if (res) { 28262306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: %s failed %d\n", __func__, 28362306a36Sopenharmony_ci __LINE__, op, res); 28462306a36Sopenharmony_ci return -1; 28562306a36Sopenharmony_ci } 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci wait_for_completion(&dev->done); 28862306a36Sopenharmony_ci if (dev->lv1_status) { 28962306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__, 29062306a36Sopenharmony_ci __LINE__, op, dev->lv1_status); 29162306a36Sopenharmony_ci return dev->lv1_status; 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: %s completed\n", __func__, __LINE__, 29562306a36Sopenharmony_ci op); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci return 0; 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ps3stor_read_write_sectors); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci/** 30362306a36Sopenharmony_ci * ps3stor_send_command - send a device command to a storage device 30462306a36Sopenharmony_ci * @dev: Pointer to a struct ps3_storage_device 30562306a36Sopenharmony_ci * @cmd: Command number 30662306a36Sopenharmony_ci * @arg1: First command argument 30762306a36Sopenharmony_ci * @arg2: Second command argument 30862306a36Sopenharmony_ci * @arg3: Third command argument 30962306a36Sopenharmony_ci * @arg4: Fourth command argument 31062306a36Sopenharmony_ci * 31162306a36Sopenharmony_ci * Returns 0 for success, -1 in case of failure to submit the command, or 31262306a36Sopenharmony_ci * an LV1 status value in case of other errors 31362306a36Sopenharmony_ci */ 31462306a36Sopenharmony_ciu64 ps3stor_send_command(struct ps3_storage_device *dev, u64 cmd, u64 arg1, 31562306a36Sopenharmony_ci u64 arg2, u64 arg3, u64 arg4) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci int res; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: send device command 0x%llx\n", __func__, 32062306a36Sopenharmony_ci __LINE__, cmd); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci init_completion(&dev->done); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci res = lv1_storage_send_device_command(dev->sbd.dev_id, cmd, arg1, 32562306a36Sopenharmony_ci arg2, arg3, arg4, &dev->tag); 32662306a36Sopenharmony_ci if (res) { 32762306a36Sopenharmony_ci dev_err(&dev->sbd.core, 32862306a36Sopenharmony_ci "%s:%u: send_device_command 0x%llx failed %d\n", 32962306a36Sopenharmony_ci __func__, __LINE__, cmd, res); 33062306a36Sopenharmony_ci return -1; 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci wait_for_completion(&dev->done); 33462306a36Sopenharmony_ci if (dev->lv1_status) { 33562306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: command 0x%llx failed 0x%llx\n", 33662306a36Sopenharmony_ci __func__, __LINE__, cmd, dev->lv1_status); 33762306a36Sopenharmony_ci return dev->lv1_status; 33862306a36Sopenharmony_ci } 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci dev_dbg(&dev->sbd.core, "%s:%u: command 0x%llx completed\n", __func__, 34162306a36Sopenharmony_ci __LINE__, cmd); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci return 0; 34462306a36Sopenharmony_ci} 34562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ps3stor_send_command); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 34962306a36Sopenharmony_ciMODULE_DESCRIPTION("PS3 Storage Bus Library"); 35062306a36Sopenharmony_ciMODULE_AUTHOR("Sony Corporation"); 351