| 1 | |
| 2 | |
| 3 | * PS3 Storage Library
|
| 4 | *
|
| 5 | * Copyright (C) 2007 Sony Computer Entertainment Inc.
|
| 6 | * Copyright 2007 Sony Corp.
|
| 7 |
|
| 8 |
|
| 9 | #include <linux/dma-mapping.h>
|
| 10 | #include <linux/module.h>
|
| 11 | #include <linux/string_choices.h>
|
| 12 |
|
| 13 | #include <asm/lv1call.h>
|
| 14 | #include <asm/ps3stor.h>
|
| 15 |
|
| 16 | |
| 17 | * A workaround for flash memory I/O errors when the internal hard disk
|
| 18 | * has not been formatted for OtherOS use. Delay disk close until flash
|
| 19 | * memory is closed.
|
| 20 |
|
| 21 |
|
| 22 | static struct ps3_flash_workaround {
|
| 23 | int flash_open;
|
| 24 | int disk_open;
|
| 25 | struct ps3_system_bus_device *disk_sbd;
|
| 26 | } ps3_flash_workaround;
|
| 27 |
|
| 28 | static int ps3stor_open_hv_device(struct ps3_system_bus_device *sbd)
|
| 29 | {
|
| 30 | int error = ps3_open_hv_device(sbd);
|
| 31 |
|
| 32 | if (error)
|
| 33 | return error;
|
| 34 |
|
| 35 | if (sbd->match_id == PS3_MATCH_ID_STOR_FLASH)
|
| 36 | ps3_flash_workaround.flash_open = 1;
|
| 37 |
|
| 38 | if (sbd->match_id == PS3_MATCH_ID_STOR_DISK)
|
| 39 | ps3_flash_workaround.disk_open = 1;
|
| 40 |
|
| 41 | return 0;
|
| 42 | }
|
| 43 |
|
| 44 | static int ps3stor_close_hv_device(struct ps3_system_bus_device *sbd)
|
| 45 | {
|
| 46 | int error;
|
| 47 |
|
| 48 | if (sbd->match_id == PS3_MATCH_ID_STOR_DISK
|
| 49 | && ps3_flash_workaround.disk_open
|
| 50 | && ps3_flash_workaround.flash_open) {
|
| 51 | ps3_flash_workaround.disk_sbd = sbd;
|
| 52 | return 0;
|
| 53 | }
|
| 54 |
|
| 55 | error = ps3_close_hv_device(sbd);
|
| 56 |
|
| 57 | if (error)
|
| 58 | return error;
|
| 59 |
|
| 60 | if (sbd->match_id == PS3_MATCH_ID_STOR_DISK)
|
| 61 | ps3_flash_workaround.disk_open = 0;
|
| 62 |
|
| 63 | if (sbd->match_id == PS3_MATCH_ID_STOR_FLASH) {
|
| 64 | ps3_flash_workaround.flash_open = 0;
|
| 65 |
|
| 66 | if (ps3_flash_workaround.disk_sbd) {
|
| 67 | ps3_close_hv_device(ps3_flash_workaround.disk_sbd);
|
| 68 | ps3_flash_workaround.disk_open = 0;
|
| 69 | ps3_flash_workaround.disk_sbd = NULL;
|
| 70 | }
|
| 71 | }
|
| 72 |
|
| 73 | return 0;
|
| 74 | }
|
| 75 |
|
| 76 | static int ps3stor_probe_access(struct ps3_storage_device *dev)
|
| 77 | {
|
| 78 | int res, error;
|
| 79 | unsigned int i;
|
| 80 | unsigned long n;
|
| 81 |
|
| 82 | if (dev->sbd.match_id == PS3_MATCH_ID_STOR_ROM) {
|
| 83 |
|
| 84 | dev->accessible_regions = 1;
|
| 85 | return 0;
|
| 86 | }
|
| 87 |
|
| 88 | error = -EPERM;
|
| 89 | for (i = 0; i < dev->num_regions; i++) {
|
| 90 | dev_dbg(&dev->sbd.core,
|
| 91 | "%s:%u: checking accessibility of region %u\n",
|
| 92 | __func__, __LINE__, i);
|
| 93 |
|
| 94 | dev->region_idx = i;
|
| 95 | res = ps3stor_read_write_sectors(dev, dev->bounce_lpar, 0, 1,
|
| 96 | 0);
|
| 97 | if (res) {
|
| 98 | dev_dbg(&dev->sbd.core, "%s:%u: read failed, "
|
| 99 | "region %u is not accessible\n", __func__,
|
| 100 | __LINE__, i);
|
| 101 | continue;
|
| 102 | }
|
| 103 |
|
| 104 | dev_dbg(&dev->sbd.core, "%s:%u: region %u is accessible\n",
|
| 105 | __func__, __LINE__, i);
|
| 106 | set_bit(i, &dev->accessible_regions);
|
| 107 |
|
| 108 |
|
| 109 | error = 0;
|
| 110 | }
|
| 111 | if (error)
|
| 112 | return error;
|
| 113 |
|
| 114 | n = hweight_long(dev->accessible_regions);
|
| 115 | if (n > 1)
|
| 116 | dev_info(&dev->sbd.core,
|
| 117 | "%s:%u: %lu accessible regions found. Only the first "
|
| 118 | "one will be used\n",
|
| 119 | __func__, __LINE__, n);
|
| 120 | dev->region_idx = __ffs(dev->accessible_regions);
|
| 121 | dev_info(&dev->sbd.core,
|
| 122 | "First accessible region has index %u start %llu size %llu\n",
|
| 123 | dev->region_idx, dev->regions[dev->region_idx].start,
|
| 124 | dev->regions[dev->region_idx].size);
|
| 125 |
|
| 126 | return 0;
|
| 127 | }
|
| 128 |
|
| 129 |
|
| 130 | |
| 131 | * ps3stor_setup - Setup a storage device before use
|
| 132 | * @dev: Pointer to a struct ps3_storage_device
|
| 133 | * @handler: Pointer to an interrupt handler
|
| 134 | *
|
| 135 | * Returns 0 for success, or an error code
|
| 136 |
|
| 137 | int ps3stor_setup(struct ps3_storage_device *dev, irq_handler_t handler)
|
| 138 | {
|
| 139 | int error, res, alignment;
|
| 140 | enum ps3_dma_page_size page_size;
|
| 141 |
|
| 142 | error = ps3stor_open_hv_device(&dev->sbd);
|
| 143 | if (error) {
|
| 144 | dev_err(&dev->sbd.core,
|
| 145 | "%s:%u: ps3_open_hv_device failed %d\n", __func__,
|
| 146 | __LINE__, error);
|
| 147 | goto fail;
|
| 148 | }
|
| 149 |
|
| 150 | error = ps3_sb_event_receive_port_setup(&dev->sbd, PS3_BINDING_CPU_ANY,
|
| 151 | &dev->irq);
|
| 152 | if (error) {
|
| 153 | dev_err(&dev->sbd.core,
|
| 154 | "%s:%u: ps3_sb_event_receive_port_setup failed %d\n",
|
| 155 | __func__, __LINE__, error);
|
| 156 | goto fail_close_device;
|
| 157 | }
|
| 158 |
|
| 159 | error = request_irq(dev->irq, handler, 0,
|
| 160 | dev->sbd.core.driver->name, dev);
|
| 161 | if (error) {
|
| 162 | dev_err(&dev->sbd.core, "%s:%u: request_irq failed %d\n",
|
| 163 | __func__, __LINE__, error);
|
| 164 | goto fail_sb_event_receive_port_destroy;
|
| 165 | }
|
| 166 |
|
| 167 | alignment = min(__ffs(dev->bounce_size),
|
| 168 | __ffs((unsigned long)dev->bounce_buf));
|
| 169 | if (alignment < 12) {
|
| 170 | dev_err(&dev->sbd.core,
|
| 171 | "%s:%u: bounce buffer not aligned (%lx at 0x%p)\n",
|
| 172 | __func__, __LINE__, dev->bounce_size, dev->bounce_buf);
|
| 173 | error = -EINVAL;
|
| 174 | goto fail_free_irq;
|
| 175 | } else if (alignment < 16)
|
| 176 | page_size = PS3_DMA_4K;
|
| 177 | else
|
| 178 | page_size = PS3_DMA_64K;
|
| 179 | dev->sbd.d_region = &dev->dma_region;
|
| 180 | ps3_dma_region_init(&dev->sbd, &dev->dma_region, page_size,
|
| 181 | PS3_DMA_OTHER, dev->bounce_buf, dev->bounce_size);
|
| 182 | res = ps3_dma_region_create(&dev->dma_region);
|
| 183 | if (res) {
|
| 184 | dev_err(&dev->sbd.core, "%s:%u: cannot create DMA region\n",
|
| 185 | __func__, __LINE__);
|
| 186 | error = -ENOMEM;
|
| 187 | goto fail_free_irq;
|
| 188 | }
|
| 189 |
|
| 190 | dev->bounce_lpar = ps3_mm_phys_to_lpar(__pa(dev->bounce_buf));
|
| 191 | dev->bounce_dma = dma_map_single(&dev->sbd.core, dev->bounce_buf,
|
| 192 | dev->bounce_size, DMA_BIDIRECTIONAL);
|
| 193 | if (dma_mapping_error(&dev->sbd.core, dev->bounce_dma)) {
|
| 194 | dev_err(&dev->sbd.core, "%s:%u: map DMA region failed\n",
|
| 195 | __func__, __LINE__);
|
| 196 | error = -ENODEV;
|
| 197 | goto fail_free_dma;
|
| 198 | }
|
| 199 |
|
| 200 | error = ps3stor_probe_access(dev);
|
| 201 | if (error) {
|
| 202 | dev_err(&dev->sbd.core, "%s:%u: No accessible regions found\n",
|
| 203 | __func__, __LINE__);
|
| 204 | goto fail_unmap_dma;
|
| 205 | }
|
| 206 | return 0;
|
| 207 |
|
| 208 | fail_unmap_dma:
|
| 209 | dma_unmap_single(&dev->sbd.core, dev->bounce_dma, dev->bounce_size,
|
| 210 | DMA_BIDIRECTIONAL);
|
| 211 | fail_free_dma:
|
| 212 | ps3_dma_region_free(&dev->dma_region);
|
| 213 | fail_free_irq:
|
| 214 | free_irq(dev->irq, dev);
|
| 215 | fail_sb_event_receive_port_destroy:
|
| 216 | ps3_sb_event_receive_port_destroy(&dev->sbd, dev->irq);
|
| 217 | fail_close_device:
|
| 218 | ps3stor_close_hv_device(&dev->sbd);
|
| 219 | fail:
|
| 220 | return error;
|
| 221 | }
|
| 222 | EXPORT_SYMBOL_GPL(ps3stor_setup);
|
| 223 |
|
| 224 |
|
| 225 | |
| 226 | * ps3stor_teardown - Tear down a storage device after use
|
| 227 | * @dev: Pointer to a struct ps3_storage_device
|
| 228 |
|
| 229 | void ps3stor_teardown(struct ps3_storage_device *dev)
|
| 230 | {
|
| 231 | int error;
|
| 232 |
|
| 233 | dma_unmap_single(&dev->sbd.core, dev->bounce_dma, dev->bounce_size,
|
| 234 | DMA_BIDIRECTIONAL);
|
| 235 | ps3_dma_region_free(&dev->dma_region);
|
| 236 |
|
| 237 | free_irq(dev->irq, dev);
|
| 238 |
|
| 239 | error = ps3_sb_event_receive_port_destroy(&dev->sbd, dev->irq);
|
| 240 | if (error)
|
| 241 | dev_err(&dev->sbd.core,
|
| 242 | "%s:%u: destroy event receive port failed %d\n",
|
| 243 | __func__, __LINE__, error);
|
| 244 |
|
| 245 | error = ps3stor_close_hv_device(&dev->sbd);
|
| 246 | if (error)
|
| 247 | dev_err(&dev->sbd.core,
|
| 248 | "%s:%u: ps3_close_hv_device failed %d\n", __func__,
|
| 249 | __LINE__, error);
|
| 250 | }
|
| 251 | EXPORT_SYMBOL_GPL(ps3stor_teardown);
|
| 252 |
|
| 253 |
|
| 254 | |
| 255 | * ps3stor_read_write_sectors - read/write from/to a storage device
|
| 256 | * @dev: Pointer to a struct ps3_storage_device
|
| 257 | * @lpar: HV logical partition address
|
| 258 | * @start_sector: First sector to read/write
|
| 259 | * @sectors: Number of sectors to read/write
|
| 260 | * @write: Flag indicating write (non-zero) or read (zero)
|
| 261 | *
|
| 262 | * Returns 0 for success, -1 in case of failure to submit the command, or
|
| 263 | * an LV1 status value in case of other errors
|
| 264 |
|
| 265 | u64 ps3stor_read_write_sectors(struct ps3_storage_device *dev, u64 lpar,
|
| 266 | u64 start_sector, u64 sectors, int write)
|
| 267 | {
|
| 268 | unsigned int region_id = dev->regions[dev->region_idx].id;
|
| 269 | const char *op = str_write_read(write);
|
| 270 | int res;
|
| 271 |
|
| 272 | dev_dbg(&dev->sbd.core, "%s:%u: %s %llu sectors starting at %llu\n",
|
| 273 | __func__, __LINE__, op, sectors, start_sector);
|
| 274 |
|
| 275 | init_completion(&dev->done);
|
| 276 | res = write ? lv1_storage_write(dev->sbd.dev_id, region_id,
|
| 277 | start_sector, sectors, 0, lpar,
|
| 278 | &dev->tag)
|
| 279 | : lv1_storage_read(dev->sbd.dev_id, region_id,
|
| 280 | start_sector, sectors, 0, lpar,
|
| 281 | &dev->tag);
|
| 282 | if (res) {
|
| 283 | dev_dbg(&dev->sbd.core, "%s:%u: %s failed %d\n", __func__,
|
| 284 | __LINE__, op, res);
|
| 285 | return -1;
|
| 286 | }
|
| 287 |
|
| 288 | wait_for_completion(&dev->done);
|
| 289 | if (dev->lv1_status) {
|
| 290 | dev_dbg(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__,
|
| 291 | __LINE__, op, dev->lv1_status);
|
| 292 | return dev->lv1_status;
|
| 293 | }
|
| 294 |
|
| 295 | dev_dbg(&dev->sbd.core, "%s:%u: %s completed\n", __func__, __LINE__,
|
| 296 | op);
|
| 297 |
|
| 298 | return 0;
|
| 299 | }
|
| 300 | EXPORT_SYMBOL_GPL(ps3stor_read_write_sectors);
|
| 301 |
|
| 302 |
|
| 303 | |
| 304 | * ps3stor_send_command - send a device command to a storage device
|
| 305 | * @dev: Pointer to a struct ps3_storage_device
|
| 306 | * @cmd: Command number
|
| 307 | * @arg1: First command argument
|
| 308 | * @arg2: Second command argument
|
| 309 | * @arg3: Third command argument
|
| 310 | * @arg4: Fourth command argument
|
| 311 | *
|
| 312 | * Returns 0 for success, -1 in case of failure to submit the command, or
|
| 313 | * an LV1 status value in case of other errors
|
| 314 |
|
| 315 | u64 ps3stor_send_command(struct ps3_storage_device *dev, u64 cmd, u64 arg1,
|
| 316 | u64 arg2, u64 arg3, u64 arg4)
|
| 317 | {
|
| 318 | int res;
|
| 319 |
|
| 320 | dev_dbg(&dev->sbd.core, "%s:%u: send device command 0x%llx\n", __func__,
|
| 321 | __LINE__, cmd);
|
| 322 |
|
| 323 | init_completion(&dev->done);
|
| 324 |
|
| 325 | res = lv1_storage_send_device_command(dev->sbd.dev_id, cmd, arg1,
|
| 326 | arg2, arg3, arg4, &dev->tag);
|
| 327 | if (res) {
|
| 328 | dev_err(&dev->sbd.core,
|
| 329 | "%s:%u: send_device_command 0x%llx failed %d\n",
|
| 330 | __func__, __LINE__, cmd, res);
|
| 331 | return -1;
|
| 332 | }
|
| 333 |
|
| 334 | wait_for_completion(&dev->done);
|
| 335 | if (dev->lv1_status) {
|
| 336 | dev_dbg(&dev->sbd.core, "%s:%u: command 0x%llx failed 0x%llx\n",
|
| 337 | __func__, __LINE__, cmd, dev->lv1_status);
|
| 338 | return dev->lv1_status;
|
| 339 | }
|
| 340 |
|
| 341 | dev_dbg(&dev->sbd.core, "%s:%u: command 0x%llx completed\n", __func__,
|
| 342 | __LINE__, cmd);
|
| 343 |
|
| 344 | return 0;
|
| 345 | }
|
| 346 | EXPORT_SYMBOL_GPL(ps3stor_send_command);
|
| 347 |
|
| 348 |
|
| 349 | MODULE_LICENSE("GPL");
|
| 350 | MODULE_DESCRIPTION("PS3 Storage Bus Library");
|
| 351 | MODULE_AUTHOR("Sony Corporation");
|