18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * PS3 Disk Storage Driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2007 Sony Computer Entertainment Inc.
68c2ecf20Sopenharmony_ci * Copyright 2007 Sony Corp.
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/ata.h>
108c2ecf20Sopenharmony_ci#include <linux/blk-mq.h>
118c2ecf20Sopenharmony_ci#include <linux/slab.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <asm/lv1call.h>
158c2ecf20Sopenharmony_ci#include <asm/ps3stor.h>
168c2ecf20Sopenharmony_ci#include <asm/firmware.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define DEVICE_NAME		"ps3disk"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define BOUNCE_SIZE		(64*1024)
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define PS3DISK_MAX_DISKS	16
248c2ecf20Sopenharmony_ci#define PS3DISK_MINORS		16
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define PS3DISK_NAME		"ps3d%c"
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistruct ps3disk_private {
318c2ecf20Sopenharmony_ci	spinlock_t lock;		/* Request queue spinlock */
328c2ecf20Sopenharmony_ci	struct request_queue *queue;
338c2ecf20Sopenharmony_ci	struct blk_mq_tag_set tag_set;
348c2ecf20Sopenharmony_ci	struct gendisk *gendisk;
358c2ecf20Sopenharmony_ci	unsigned int blocking_factor;
368c2ecf20Sopenharmony_ci	struct request *req;
378c2ecf20Sopenharmony_ci	u64 raw_capacity;
388c2ecf20Sopenharmony_ci	unsigned char model[ATA_ID_PROD_LEN+1];
398c2ecf20Sopenharmony_ci};
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define LV1_STORAGE_SEND_ATA_COMMAND	(2)
438c2ecf20Sopenharmony_ci#define LV1_STORAGE_ATA_HDDOUT		(0x23)
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistruct lv1_ata_cmnd_block {
468c2ecf20Sopenharmony_ci	u16	features;
478c2ecf20Sopenharmony_ci	u16	sector_count;
488c2ecf20Sopenharmony_ci	u16	LBA_low;
498c2ecf20Sopenharmony_ci	u16	LBA_mid;
508c2ecf20Sopenharmony_ci	u16	LBA_high;
518c2ecf20Sopenharmony_ci	u8	device;
528c2ecf20Sopenharmony_ci	u8	command;
538c2ecf20Sopenharmony_ci	u32	is_ext;
548c2ecf20Sopenharmony_ci	u32	proto;
558c2ecf20Sopenharmony_ci	u32	in_out;
568c2ecf20Sopenharmony_ci	u32	size;
578c2ecf20Sopenharmony_ci	u64	buffer;
588c2ecf20Sopenharmony_ci	u32	arglen;
598c2ecf20Sopenharmony_ci};
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cienum lv1_ata_proto {
628c2ecf20Sopenharmony_ci	NON_DATA_PROTO     = 0,
638c2ecf20Sopenharmony_ci	PIO_DATA_IN_PROTO  = 1,
648c2ecf20Sopenharmony_ci	PIO_DATA_OUT_PROTO = 2,
658c2ecf20Sopenharmony_ci	DMA_PROTO = 3
668c2ecf20Sopenharmony_ci};
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cienum lv1_ata_in_out {
698c2ecf20Sopenharmony_ci	DIR_WRITE = 0,			/* memory -> device */
708c2ecf20Sopenharmony_ci	DIR_READ = 1			/* device -> memory */
718c2ecf20Sopenharmony_ci};
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int ps3disk_major;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic const struct block_device_operations ps3disk_fops = {
778c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
788c2ecf20Sopenharmony_ci};
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic void ps3disk_scatter_gather(struct ps3_storage_device *dev,
828c2ecf20Sopenharmony_ci				   struct request *req, int gather)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	unsigned int offset = 0;
858c2ecf20Sopenharmony_ci	struct req_iterator iter;
868c2ecf20Sopenharmony_ci	struct bio_vec bvec;
878c2ecf20Sopenharmony_ci	unsigned int i = 0;
888c2ecf20Sopenharmony_ci	size_t size;
898c2ecf20Sopenharmony_ci	void *buf;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	rq_for_each_segment(bvec, req, iter) {
928c2ecf20Sopenharmony_ci		unsigned long flags;
938c2ecf20Sopenharmony_ci		dev_dbg(&dev->sbd.core, "%s:%u: bio %u: %u sectors from %llu\n",
948c2ecf20Sopenharmony_ci			__func__, __LINE__, i, bio_sectors(iter.bio),
958c2ecf20Sopenharmony_ci			iter.bio->bi_iter.bi_sector);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci		size = bvec.bv_len;
988c2ecf20Sopenharmony_ci		buf = bvec_kmap_irq(&bvec, &flags);
998c2ecf20Sopenharmony_ci		if (gather)
1008c2ecf20Sopenharmony_ci			memcpy(dev->bounce_buf+offset, buf, size);
1018c2ecf20Sopenharmony_ci		else
1028c2ecf20Sopenharmony_ci			memcpy(buf, dev->bounce_buf+offset, size);
1038c2ecf20Sopenharmony_ci		offset += size;
1048c2ecf20Sopenharmony_ci		flush_kernel_dcache_page(bvec.bv_page);
1058c2ecf20Sopenharmony_ci		bvec_kunmap_irq(buf, &flags);
1068c2ecf20Sopenharmony_ci		i++;
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic blk_status_t ps3disk_submit_request_sg(struct ps3_storage_device *dev,
1118c2ecf20Sopenharmony_ci					      struct request *req)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	struct ps3disk_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
1148c2ecf20Sopenharmony_ci	int write = rq_data_dir(req), res;
1158c2ecf20Sopenharmony_ci	const char *op = write ? "write" : "read";
1168c2ecf20Sopenharmony_ci	u64 start_sector, sectors;
1178c2ecf20Sopenharmony_ci	unsigned int region_id = dev->regions[dev->region_idx].id;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci#ifdef DEBUG
1208c2ecf20Sopenharmony_ci	unsigned int n = 0;
1218c2ecf20Sopenharmony_ci	struct bio_vec bv;
1228c2ecf20Sopenharmony_ci	struct req_iterator iter;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	rq_for_each_segment(bv, req, iter)
1258c2ecf20Sopenharmony_ci		n++;
1268c2ecf20Sopenharmony_ci	dev_dbg(&dev->sbd.core,
1278c2ecf20Sopenharmony_ci		"%s:%u: %s req has %u bvecs for %u sectors\n",
1288c2ecf20Sopenharmony_ci		__func__, __LINE__, op, n, blk_rq_sectors(req));
1298c2ecf20Sopenharmony_ci#endif
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	start_sector = blk_rq_pos(req) * priv->blocking_factor;
1328c2ecf20Sopenharmony_ci	sectors = blk_rq_sectors(req) * priv->blocking_factor;
1338c2ecf20Sopenharmony_ci	dev_dbg(&dev->sbd.core, "%s:%u: %s %llu sectors starting at %llu\n",
1348c2ecf20Sopenharmony_ci		__func__, __LINE__, op, sectors, start_sector);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	if (write) {
1378c2ecf20Sopenharmony_ci		ps3disk_scatter_gather(dev, req, 1);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci		res = lv1_storage_write(dev->sbd.dev_id, region_id,
1408c2ecf20Sopenharmony_ci					start_sector, sectors, 0,
1418c2ecf20Sopenharmony_ci					dev->bounce_lpar, &dev->tag);
1428c2ecf20Sopenharmony_ci	} else {
1438c2ecf20Sopenharmony_ci		res = lv1_storage_read(dev->sbd.dev_id, region_id,
1448c2ecf20Sopenharmony_ci				       start_sector, sectors, 0,
1458c2ecf20Sopenharmony_ci				       dev->bounce_lpar, &dev->tag);
1468c2ecf20Sopenharmony_ci	}
1478c2ecf20Sopenharmony_ci	if (res) {
1488c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: %s failed %d\n", __func__,
1498c2ecf20Sopenharmony_ci			__LINE__, op, res);
1508c2ecf20Sopenharmony_ci		return BLK_STS_IOERR;
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	priv->req = req;
1548c2ecf20Sopenharmony_ci	return BLK_STS_OK;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic blk_status_t ps3disk_submit_flush_request(struct ps3_storage_device *dev,
1588c2ecf20Sopenharmony_ci						 struct request *req)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	struct ps3disk_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
1618c2ecf20Sopenharmony_ci	u64 res;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	dev_dbg(&dev->sbd.core, "%s:%u: flush request\n", __func__, __LINE__);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	res = lv1_storage_send_device_command(dev->sbd.dev_id,
1668c2ecf20Sopenharmony_ci					      LV1_STORAGE_ATA_HDDOUT, 0, 0, 0,
1678c2ecf20Sopenharmony_ci					      0, &dev->tag);
1688c2ecf20Sopenharmony_ci	if (res) {
1698c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: sync cache failed 0x%llx\n",
1708c2ecf20Sopenharmony_ci			__func__, __LINE__, res);
1718c2ecf20Sopenharmony_ci		return BLK_STS_IOERR;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	priv->req = req;
1758c2ecf20Sopenharmony_ci	return BLK_STS_OK;
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic blk_status_t ps3disk_do_request(struct ps3_storage_device *dev,
1798c2ecf20Sopenharmony_ci				       struct request *req)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	dev_dbg(&dev->sbd.core, "%s:%u\n", __func__, __LINE__);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	switch (req_op(req)) {
1848c2ecf20Sopenharmony_ci	case REQ_OP_FLUSH:
1858c2ecf20Sopenharmony_ci		return ps3disk_submit_flush_request(dev, req);
1868c2ecf20Sopenharmony_ci	case REQ_OP_READ:
1878c2ecf20Sopenharmony_ci	case REQ_OP_WRITE:
1888c2ecf20Sopenharmony_ci		return ps3disk_submit_request_sg(dev, req);
1898c2ecf20Sopenharmony_ci	default:
1908c2ecf20Sopenharmony_ci		blk_dump_rq_flags(req, DEVICE_NAME " bad request");
1918c2ecf20Sopenharmony_ci		return BLK_STS_IOERR;
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic blk_status_t ps3disk_queue_rq(struct blk_mq_hw_ctx *hctx,
1968c2ecf20Sopenharmony_ci				     const struct blk_mq_queue_data *bd)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	struct request_queue *q = hctx->queue;
1998c2ecf20Sopenharmony_ci	struct ps3_storage_device *dev = q->queuedata;
2008c2ecf20Sopenharmony_ci	struct ps3disk_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
2018c2ecf20Sopenharmony_ci	blk_status_t ret;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	blk_mq_start_request(bd->rq);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	spin_lock_irq(&priv->lock);
2068c2ecf20Sopenharmony_ci	ret = ps3disk_do_request(dev, bd->rq);
2078c2ecf20Sopenharmony_ci	spin_unlock_irq(&priv->lock);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	return ret;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic irqreturn_t ps3disk_interrupt(int irq, void *data)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	struct ps3_storage_device *dev = data;
2158c2ecf20Sopenharmony_ci	struct ps3disk_private *priv;
2168c2ecf20Sopenharmony_ci	struct request *req;
2178c2ecf20Sopenharmony_ci	int res, read;
2188c2ecf20Sopenharmony_ci	blk_status_t error;
2198c2ecf20Sopenharmony_ci	u64 tag, status;
2208c2ecf20Sopenharmony_ci	const char *op;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	res = lv1_storage_get_async_status(dev->sbd.dev_id, &tag, &status);
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	if (tag != dev->tag)
2258c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core,
2268c2ecf20Sopenharmony_ci			"%s:%u: tag mismatch, got %llx, expected %llx\n",
2278c2ecf20Sopenharmony_ci			__func__, __LINE__, tag, dev->tag);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	if (res) {
2308c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: res=%d status=0x%llx\n",
2318c2ecf20Sopenharmony_ci			__func__, __LINE__, res, status);
2328c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	priv = ps3_system_bus_get_drvdata(&dev->sbd);
2368c2ecf20Sopenharmony_ci	req = priv->req;
2378c2ecf20Sopenharmony_ci	if (!req) {
2388c2ecf20Sopenharmony_ci		dev_dbg(&dev->sbd.core,
2398c2ecf20Sopenharmony_ci			"%s:%u non-block layer request completed\n", __func__,
2408c2ecf20Sopenharmony_ci			__LINE__);
2418c2ecf20Sopenharmony_ci		dev->lv1_status = status;
2428c2ecf20Sopenharmony_ci		complete(&dev->done);
2438c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	if (req_op(req) == REQ_OP_FLUSH) {
2478c2ecf20Sopenharmony_ci		read = 0;
2488c2ecf20Sopenharmony_ci		op = "flush";
2498c2ecf20Sopenharmony_ci	} else {
2508c2ecf20Sopenharmony_ci		read = !rq_data_dir(req);
2518c2ecf20Sopenharmony_ci		op = read ? "read" : "write";
2528c2ecf20Sopenharmony_ci	}
2538c2ecf20Sopenharmony_ci	if (status) {
2548c2ecf20Sopenharmony_ci		dev_dbg(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__,
2558c2ecf20Sopenharmony_ci			__LINE__, op, status);
2568c2ecf20Sopenharmony_ci		error = BLK_STS_IOERR;
2578c2ecf20Sopenharmony_ci	} else {
2588c2ecf20Sopenharmony_ci		dev_dbg(&dev->sbd.core, "%s:%u: %s completed\n", __func__,
2598c2ecf20Sopenharmony_ci			__LINE__, op);
2608c2ecf20Sopenharmony_ci		error = 0;
2618c2ecf20Sopenharmony_ci		if (read)
2628c2ecf20Sopenharmony_ci			ps3disk_scatter_gather(dev, req, 0);
2638c2ecf20Sopenharmony_ci	}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	spin_lock(&priv->lock);
2668c2ecf20Sopenharmony_ci	priv->req = NULL;
2678c2ecf20Sopenharmony_ci	blk_mq_end_request(req, error);
2688c2ecf20Sopenharmony_ci	spin_unlock(&priv->lock);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	blk_mq_run_hw_queues(priv->queue, true);
2718c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2728c2ecf20Sopenharmony_ci}
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_cistatic int ps3disk_sync_cache(struct ps3_storage_device *dev)
2758c2ecf20Sopenharmony_ci{
2768c2ecf20Sopenharmony_ci	u64 res;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	dev_dbg(&dev->sbd.core, "%s:%u: sync cache\n", __func__, __LINE__);
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	res = ps3stor_send_command(dev, LV1_STORAGE_ATA_HDDOUT, 0, 0, 0, 0);
2818c2ecf20Sopenharmony_ci	if (res) {
2828c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: sync cache failed 0x%llx\n",
2838c2ecf20Sopenharmony_ci			__func__, __LINE__, res);
2848c2ecf20Sopenharmony_ci		return -EIO;
2858c2ecf20Sopenharmony_ci	}
2868c2ecf20Sopenharmony_ci	return 0;
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci/* ATA helpers copied from drivers/ata/libata-core.c */
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_cistatic void swap_buf_le16(u16 *buf, unsigned int buf_words)
2938c2ecf20Sopenharmony_ci{
2948c2ecf20Sopenharmony_ci#ifdef __BIG_ENDIAN
2958c2ecf20Sopenharmony_ci	unsigned int i;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	for (i = 0; i < buf_words; i++)
2988c2ecf20Sopenharmony_ci		buf[i] = le16_to_cpu(buf[i]);
2998c2ecf20Sopenharmony_ci#endif /* __BIG_ENDIAN */
3008c2ecf20Sopenharmony_ci}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_cistatic u64 ata_id_n_sectors(const u16 *id)
3038c2ecf20Sopenharmony_ci{
3048c2ecf20Sopenharmony_ci	if (ata_id_has_lba(id)) {
3058c2ecf20Sopenharmony_ci		if (ata_id_has_lba48(id))
3068c2ecf20Sopenharmony_ci			return ata_id_u64(id, 100);
3078c2ecf20Sopenharmony_ci		else
3088c2ecf20Sopenharmony_ci			return ata_id_u32(id, 60);
3098c2ecf20Sopenharmony_ci	} else {
3108c2ecf20Sopenharmony_ci		if (ata_id_current_chs_valid(id))
3118c2ecf20Sopenharmony_ci			return ata_id_u32(id, 57);
3128c2ecf20Sopenharmony_ci		else
3138c2ecf20Sopenharmony_ci			return id[1] * id[3] * id[6];
3148c2ecf20Sopenharmony_ci	}
3158c2ecf20Sopenharmony_ci}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_cistatic void ata_id_string(const u16 *id, unsigned char *s, unsigned int ofs,
3188c2ecf20Sopenharmony_ci			  unsigned int len)
3198c2ecf20Sopenharmony_ci{
3208c2ecf20Sopenharmony_ci	unsigned int c;
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci	while (len > 0) {
3238c2ecf20Sopenharmony_ci		c = id[ofs] >> 8;
3248c2ecf20Sopenharmony_ci		*s = c;
3258c2ecf20Sopenharmony_ci		s++;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci		c = id[ofs] & 0xff;
3288c2ecf20Sopenharmony_ci		*s = c;
3298c2ecf20Sopenharmony_ci		s++;
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci		ofs++;
3328c2ecf20Sopenharmony_ci		len -= 2;
3338c2ecf20Sopenharmony_ci	}
3348c2ecf20Sopenharmony_ci}
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_cistatic void ata_id_c_string(const u16 *id, unsigned char *s, unsigned int ofs,
3378c2ecf20Sopenharmony_ci			    unsigned int len)
3388c2ecf20Sopenharmony_ci{
3398c2ecf20Sopenharmony_ci	unsigned char *p;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	WARN_ON(!(len & 1));
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	ata_id_string(id, s, ofs, len - 1);
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	p = s + strnlen(s, len - 1);
3468c2ecf20Sopenharmony_ci	while (p > s && p[-1] == ' ')
3478c2ecf20Sopenharmony_ci		p--;
3488c2ecf20Sopenharmony_ci	*p = '\0';
3498c2ecf20Sopenharmony_ci}
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_cistatic int ps3disk_identify(struct ps3_storage_device *dev)
3528c2ecf20Sopenharmony_ci{
3538c2ecf20Sopenharmony_ci	struct ps3disk_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
3548c2ecf20Sopenharmony_ci	struct lv1_ata_cmnd_block ata_cmnd;
3558c2ecf20Sopenharmony_ci	u16 *id = dev->bounce_buf;
3568c2ecf20Sopenharmony_ci	u64 res;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	dev_dbg(&dev->sbd.core, "%s:%u: identify disk\n", __func__, __LINE__);
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	memset(&ata_cmnd, 0, sizeof(struct lv1_ata_cmnd_block));
3618c2ecf20Sopenharmony_ci	ata_cmnd.command = ATA_CMD_ID_ATA;
3628c2ecf20Sopenharmony_ci	ata_cmnd.sector_count = 1;
3638c2ecf20Sopenharmony_ci	ata_cmnd.size = ata_cmnd.arglen = ATA_ID_WORDS * 2;
3648c2ecf20Sopenharmony_ci	ata_cmnd.buffer = dev->bounce_lpar;
3658c2ecf20Sopenharmony_ci	ata_cmnd.proto = PIO_DATA_IN_PROTO;
3668c2ecf20Sopenharmony_ci	ata_cmnd.in_out = DIR_READ;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	res = ps3stor_send_command(dev, LV1_STORAGE_SEND_ATA_COMMAND,
3698c2ecf20Sopenharmony_ci				   ps3_mm_phys_to_lpar(__pa(&ata_cmnd)),
3708c2ecf20Sopenharmony_ci				   sizeof(ata_cmnd), ata_cmnd.buffer,
3718c2ecf20Sopenharmony_ci				   ata_cmnd.arglen);
3728c2ecf20Sopenharmony_ci	if (res) {
3738c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: identify disk failed 0x%llx\n",
3748c2ecf20Sopenharmony_ci			__func__, __LINE__, res);
3758c2ecf20Sopenharmony_ci		return -EIO;
3768c2ecf20Sopenharmony_ci	}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	swap_buf_le16(id, ATA_ID_WORDS);
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	/* All we're interested in are raw capacity and model name */
3818c2ecf20Sopenharmony_ci	priv->raw_capacity = ata_id_n_sectors(id);
3828c2ecf20Sopenharmony_ci	ata_id_c_string(id, priv->model, ATA_ID_PROD, sizeof(priv->model));
3838c2ecf20Sopenharmony_ci	return 0;
3848c2ecf20Sopenharmony_ci}
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_cistatic unsigned long ps3disk_mask;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(ps3disk_mask_mutex);
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_cistatic const struct blk_mq_ops ps3disk_mq_ops = {
3918c2ecf20Sopenharmony_ci	.queue_rq	= ps3disk_queue_rq,
3928c2ecf20Sopenharmony_ci};
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_cistatic int ps3disk_probe(struct ps3_system_bus_device *_dev)
3958c2ecf20Sopenharmony_ci{
3968c2ecf20Sopenharmony_ci	struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core);
3978c2ecf20Sopenharmony_ci	struct ps3disk_private *priv;
3988c2ecf20Sopenharmony_ci	int error;
3998c2ecf20Sopenharmony_ci	unsigned int devidx;
4008c2ecf20Sopenharmony_ci	struct request_queue *queue;
4018c2ecf20Sopenharmony_ci	struct gendisk *gendisk;
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	if (dev->blk_size < 512) {
4048c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core,
4058c2ecf20Sopenharmony_ci			"%s:%u: cannot handle block size %llu\n", __func__,
4068c2ecf20Sopenharmony_ci			__LINE__, dev->blk_size);
4078c2ecf20Sopenharmony_ci		return -EINVAL;
4088c2ecf20Sopenharmony_ci	}
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci	BUILD_BUG_ON(PS3DISK_MAX_DISKS > BITS_PER_LONG);
4118c2ecf20Sopenharmony_ci	mutex_lock(&ps3disk_mask_mutex);
4128c2ecf20Sopenharmony_ci	devidx = find_first_zero_bit(&ps3disk_mask, PS3DISK_MAX_DISKS);
4138c2ecf20Sopenharmony_ci	if (devidx >= PS3DISK_MAX_DISKS) {
4148c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: Too many disks\n", __func__,
4158c2ecf20Sopenharmony_ci			__LINE__);
4168c2ecf20Sopenharmony_ci		mutex_unlock(&ps3disk_mask_mutex);
4178c2ecf20Sopenharmony_ci		return -ENOSPC;
4188c2ecf20Sopenharmony_ci	}
4198c2ecf20Sopenharmony_ci	__set_bit(devidx, &ps3disk_mask);
4208c2ecf20Sopenharmony_ci	mutex_unlock(&ps3disk_mask_mutex);
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
4238c2ecf20Sopenharmony_ci	if (!priv) {
4248c2ecf20Sopenharmony_ci		error = -ENOMEM;
4258c2ecf20Sopenharmony_ci		goto fail;
4268c2ecf20Sopenharmony_ci	}
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	ps3_system_bus_set_drvdata(_dev, priv);
4298c2ecf20Sopenharmony_ci	spin_lock_init(&priv->lock);
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	dev->bounce_size = BOUNCE_SIZE;
4328c2ecf20Sopenharmony_ci	dev->bounce_buf = kmalloc(BOUNCE_SIZE, GFP_DMA);
4338c2ecf20Sopenharmony_ci	if (!dev->bounce_buf) {
4348c2ecf20Sopenharmony_ci		error = -ENOMEM;
4358c2ecf20Sopenharmony_ci		goto fail_free_priv;
4368c2ecf20Sopenharmony_ci	}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	error = ps3stor_setup(dev, ps3disk_interrupt);
4398c2ecf20Sopenharmony_ci	if (error)
4408c2ecf20Sopenharmony_ci		goto fail_free_bounce;
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	ps3disk_identify(dev);
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	queue = blk_mq_init_sq_queue(&priv->tag_set, &ps3disk_mq_ops, 1,
4458c2ecf20Sopenharmony_ci					BLK_MQ_F_SHOULD_MERGE);
4468c2ecf20Sopenharmony_ci	if (IS_ERR(queue)) {
4478c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: blk_mq_init_queue failed\n",
4488c2ecf20Sopenharmony_ci			__func__, __LINE__);
4498c2ecf20Sopenharmony_ci		error = PTR_ERR(queue);
4508c2ecf20Sopenharmony_ci		goto fail_teardown;
4518c2ecf20Sopenharmony_ci	}
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	priv->queue = queue;
4548c2ecf20Sopenharmony_ci	queue->queuedata = dev;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	blk_queue_max_hw_sectors(queue, dev->bounce_size >> 9);
4578c2ecf20Sopenharmony_ci	blk_queue_dma_alignment(queue, dev->blk_size-1);
4588c2ecf20Sopenharmony_ci	blk_queue_logical_block_size(queue, dev->blk_size);
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ci	blk_queue_write_cache(queue, true, false);
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	blk_queue_max_segments(queue, -1);
4638c2ecf20Sopenharmony_ci	blk_queue_max_segment_size(queue, dev->bounce_size);
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ci	gendisk = alloc_disk(PS3DISK_MINORS);
4668c2ecf20Sopenharmony_ci	if (!gendisk) {
4678c2ecf20Sopenharmony_ci		dev_err(&dev->sbd.core, "%s:%u: alloc_disk failed\n", __func__,
4688c2ecf20Sopenharmony_ci			__LINE__);
4698c2ecf20Sopenharmony_ci		error = -ENOMEM;
4708c2ecf20Sopenharmony_ci		goto fail_cleanup_queue;
4718c2ecf20Sopenharmony_ci	}
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	priv->gendisk = gendisk;
4748c2ecf20Sopenharmony_ci	gendisk->major = ps3disk_major;
4758c2ecf20Sopenharmony_ci	gendisk->first_minor = devidx * PS3DISK_MINORS;
4768c2ecf20Sopenharmony_ci	gendisk->fops = &ps3disk_fops;
4778c2ecf20Sopenharmony_ci	gendisk->queue = queue;
4788c2ecf20Sopenharmony_ci	gendisk->private_data = dev;
4798c2ecf20Sopenharmony_ci	snprintf(gendisk->disk_name, sizeof(gendisk->disk_name), PS3DISK_NAME,
4808c2ecf20Sopenharmony_ci		 devidx+'a');
4818c2ecf20Sopenharmony_ci	priv->blocking_factor = dev->blk_size >> 9;
4828c2ecf20Sopenharmony_ci	set_capacity(gendisk,
4838c2ecf20Sopenharmony_ci		     dev->regions[dev->region_idx].size*priv->blocking_factor);
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	dev_info(&dev->sbd.core,
4868c2ecf20Sopenharmony_ci		 "%s is a %s (%llu MiB total, %llu MiB for OtherOS)\n",
4878c2ecf20Sopenharmony_ci		 gendisk->disk_name, priv->model, priv->raw_capacity >> 11,
4888c2ecf20Sopenharmony_ci		 get_capacity(gendisk) >> 11);
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	device_add_disk(&dev->sbd.core, gendisk, NULL);
4918c2ecf20Sopenharmony_ci	return 0;
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_cifail_cleanup_queue:
4948c2ecf20Sopenharmony_ci	blk_cleanup_queue(queue);
4958c2ecf20Sopenharmony_ci	blk_mq_free_tag_set(&priv->tag_set);
4968c2ecf20Sopenharmony_cifail_teardown:
4978c2ecf20Sopenharmony_ci	ps3stor_teardown(dev);
4988c2ecf20Sopenharmony_cifail_free_bounce:
4998c2ecf20Sopenharmony_ci	kfree(dev->bounce_buf);
5008c2ecf20Sopenharmony_cifail_free_priv:
5018c2ecf20Sopenharmony_ci	kfree(priv);
5028c2ecf20Sopenharmony_ci	ps3_system_bus_set_drvdata(_dev, NULL);
5038c2ecf20Sopenharmony_cifail:
5048c2ecf20Sopenharmony_ci	mutex_lock(&ps3disk_mask_mutex);
5058c2ecf20Sopenharmony_ci	__clear_bit(devidx, &ps3disk_mask);
5068c2ecf20Sopenharmony_ci	mutex_unlock(&ps3disk_mask_mutex);
5078c2ecf20Sopenharmony_ci	return error;
5088c2ecf20Sopenharmony_ci}
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_cistatic int ps3disk_remove(struct ps3_system_bus_device *_dev)
5118c2ecf20Sopenharmony_ci{
5128c2ecf20Sopenharmony_ci	struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core);
5138c2ecf20Sopenharmony_ci	struct ps3disk_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	mutex_lock(&ps3disk_mask_mutex);
5168c2ecf20Sopenharmony_ci	__clear_bit(MINOR(disk_devt(priv->gendisk)) / PS3DISK_MINORS,
5178c2ecf20Sopenharmony_ci		    &ps3disk_mask);
5188c2ecf20Sopenharmony_ci	mutex_unlock(&ps3disk_mask_mutex);
5198c2ecf20Sopenharmony_ci	del_gendisk(priv->gendisk);
5208c2ecf20Sopenharmony_ci	blk_cleanup_queue(priv->queue);
5218c2ecf20Sopenharmony_ci	blk_mq_free_tag_set(&priv->tag_set);
5228c2ecf20Sopenharmony_ci	put_disk(priv->gendisk);
5238c2ecf20Sopenharmony_ci	dev_notice(&dev->sbd.core, "Synchronizing disk cache\n");
5248c2ecf20Sopenharmony_ci	ps3disk_sync_cache(dev);
5258c2ecf20Sopenharmony_ci	ps3stor_teardown(dev);
5268c2ecf20Sopenharmony_ci	kfree(dev->bounce_buf);
5278c2ecf20Sopenharmony_ci	kfree(priv);
5288c2ecf20Sopenharmony_ci	ps3_system_bus_set_drvdata(_dev, NULL);
5298c2ecf20Sopenharmony_ci	return 0;
5308c2ecf20Sopenharmony_ci}
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_cistatic struct ps3_system_bus_driver ps3disk = {
5338c2ecf20Sopenharmony_ci	.match_id	= PS3_MATCH_ID_STOR_DISK,
5348c2ecf20Sopenharmony_ci	.core.name	= DEVICE_NAME,
5358c2ecf20Sopenharmony_ci	.core.owner	= THIS_MODULE,
5368c2ecf20Sopenharmony_ci	.probe		= ps3disk_probe,
5378c2ecf20Sopenharmony_ci	.remove		= ps3disk_remove,
5388c2ecf20Sopenharmony_ci	.shutdown	= ps3disk_remove,
5398c2ecf20Sopenharmony_ci};
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_cistatic int __init ps3disk_init(void)
5438c2ecf20Sopenharmony_ci{
5448c2ecf20Sopenharmony_ci	int error;
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	if (!firmware_has_feature(FW_FEATURE_PS3_LV1))
5478c2ecf20Sopenharmony_ci		return -ENODEV;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	error = register_blkdev(0, DEVICE_NAME);
5508c2ecf20Sopenharmony_ci	if (error <= 0) {
5518c2ecf20Sopenharmony_ci		printk(KERN_ERR "%s:%u: register_blkdev failed %d\n", __func__,
5528c2ecf20Sopenharmony_ci		       __LINE__, error);
5538c2ecf20Sopenharmony_ci		return error;
5548c2ecf20Sopenharmony_ci	}
5558c2ecf20Sopenharmony_ci	ps3disk_major = error;
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci	pr_info("%s:%u: registered block device major %d\n", __func__,
5588c2ecf20Sopenharmony_ci		__LINE__, ps3disk_major);
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	error = ps3_system_bus_driver_register(&ps3disk);
5618c2ecf20Sopenharmony_ci	if (error)
5628c2ecf20Sopenharmony_ci		unregister_blkdev(ps3disk_major, DEVICE_NAME);
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci	return error;
5658c2ecf20Sopenharmony_ci}
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_cistatic void __exit ps3disk_exit(void)
5688c2ecf20Sopenharmony_ci{
5698c2ecf20Sopenharmony_ci	ps3_system_bus_driver_unregister(&ps3disk);
5708c2ecf20Sopenharmony_ci	unregister_blkdev(ps3disk_major, DEVICE_NAME);
5718c2ecf20Sopenharmony_ci}
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_cimodule_init(ps3disk_init);
5748c2ecf20Sopenharmony_cimodule_exit(ps3disk_exit);
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
5778c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PS3 Disk Storage Driver");
5788c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sony Corporation");
5798c2ecf20Sopenharmony_ciMODULE_ALIAS(PS3_MODULE_ALIAS_STOR_DISK);
580