18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Xpram.c -- the S/390 expanded memory RAM-disk
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * significant parts of this code are based on
68c2ecf20Sopenharmony_ci * the sbull device driver presented in
78c2ecf20Sopenharmony_ci * A. Rubini: Linux Device Drivers
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Author of XPRAM specific coding: Reinhard Buendgen
108c2ecf20Sopenharmony_ci *                                  buendgen@de.ibm.com
118c2ecf20Sopenharmony_ci * Rewrite for 2.5: Martin Schwidefsky <schwidefsky@de.ibm.com>
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * External interfaces:
148c2ecf20Sopenharmony_ci *   Interfaces to linux kernel
158c2ecf20Sopenharmony_ci *        xpram_setup: read kernel parameters
168c2ecf20Sopenharmony_ci *   Device specific file operations
178c2ecf20Sopenharmony_ci *        xpram_iotcl
188c2ecf20Sopenharmony_ci *        xpram_open
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci * "ad-hoc" partitioning:
218c2ecf20Sopenharmony_ci *    the expanded memory can be partitioned among several devices
228c2ecf20Sopenharmony_ci *    (with different minors). The partitioning set up can be
238c2ecf20Sopenharmony_ci *    set by kernel or module parameters (int devs & int sizes[])
248c2ecf20Sopenharmony_ci *
258c2ecf20Sopenharmony_ci * Potential future improvements:
268c2ecf20Sopenharmony_ci *   generic hard disk support to replace ad-hoc partitioning
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define KMSG_COMPONENT "xpram"
308c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#include <linux/module.h>
338c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
348c2ecf20Sopenharmony_ci#include <linux/ctype.h>  /* isdigit, isxdigit */
358c2ecf20Sopenharmony_ci#include <linux/errno.h>
368c2ecf20Sopenharmony_ci#include <linux/init.h>
378c2ecf20Sopenharmony_ci#include <linux/blkdev.h>
388c2ecf20Sopenharmony_ci#include <linux/blkpg.h>
398c2ecf20Sopenharmony_ci#include <linux/hdreg.h>  /* HDIO_GETGEO */
408c2ecf20Sopenharmony_ci#include <linux/device.h>
418c2ecf20Sopenharmony_ci#include <linux/bio.h>
428c2ecf20Sopenharmony_ci#include <linux/suspend.h>
438c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
448c2ecf20Sopenharmony_ci#include <linux/gfp.h>
458c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define XPRAM_NAME	"xpram"
488c2ecf20Sopenharmony_ci#define XPRAM_DEVS	1	/* one partition */
498c2ecf20Sopenharmony_ci#define XPRAM_MAX_DEVS	32	/* maximal number of devices (partitions) */
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_citypedef struct {
528c2ecf20Sopenharmony_ci	unsigned int	size;		/* size of xpram segment in pages */
538c2ecf20Sopenharmony_ci	unsigned int	offset;		/* start page of xpram segment */
548c2ecf20Sopenharmony_ci} xpram_device_t;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic xpram_device_t xpram_devices[XPRAM_MAX_DEVS];
578c2ecf20Sopenharmony_cistatic unsigned int xpram_sizes[XPRAM_MAX_DEVS];
588c2ecf20Sopenharmony_cistatic struct gendisk *xpram_disks[XPRAM_MAX_DEVS];
598c2ecf20Sopenharmony_cistatic struct request_queue *xpram_queues[XPRAM_MAX_DEVS];
608c2ecf20Sopenharmony_cistatic unsigned int xpram_pages;
618c2ecf20Sopenharmony_cistatic int xpram_devs;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/*
648c2ecf20Sopenharmony_ci * Parameter parsing functions.
658c2ecf20Sopenharmony_ci */
668c2ecf20Sopenharmony_cistatic int devs = XPRAM_DEVS;
678c2ecf20Sopenharmony_cistatic char *sizes[XPRAM_MAX_DEVS];
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cimodule_param(devs, int, 0);
708c2ecf20Sopenharmony_cimodule_param_array(sizes, charp, NULL, 0);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ciMODULE_PARM_DESC(devs, "number of devices (\"partitions\"), " \
738c2ecf20Sopenharmony_ci		 "the default is " __MODULE_STRING(XPRAM_DEVS) "\n");
748c2ecf20Sopenharmony_ciMODULE_PARM_DESC(sizes, "list of device (partition) sizes " \
758c2ecf20Sopenharmony_ci		 "the defaults are 0s \n" \
768c2ecf20Sopenharmony_ci		 "All devices with size 0 equally partition the "
778c2ecf20Sopenharmony_ci		 "remaining space on the expanded strorage not "
788c2ecf20Sopenharmony_ci		 "claimed by explicit sizes\n");
798c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci/*
828c2ecf20Sopenharmony_ci * Copy expanded memory page (4kB) into main memory
838c2ecf20Sopenharmony_ci * Arguments
848c2ecf20Sopenharmony_ci *           page_addr:    address of target page
858c2ecf20Sopenharmony_ci *           xpage_index:  index of expandeded memory page
868c2ecf20Sopenharmony_ci * Return value
878c2ecf20Sopenharmony_ci *           0:            if operation succeeds
888c2ecf20Sopenharmony_ci *           -EIO:         if pgin failed
898c2ecf20Sopenharmony_ci *           -ENXIO:       if xpram has vanished
908c2ecf20Sopenharmony_ci */
918c2ecf20Sopenharmony_cistatic int xpram_page_in (unsigned long page_addr, unsigned int xpage_index)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	int cc = 2;	/* return unused cc 2 if pgin traps */
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	asm volatile(
968c2ecf20Sopenharmony_ci		"	.insn	rre,0xb22e0000,%1,%2\n"  /* pgin %1,%2 */
978c2ecf20Sopenharmony_ci		"0:	ipm	%0\n"
988c2ecf20Sopenharmony_ci		"	srl	%0,28\n"
998c2ecf20Sopenharmony_ci		"1:\n"
1008c2ecf20Sopenharmony_ci		EX_TABLE(0b,1b)
1018c2ecf20Sopenharmony_ci		: "+d" (cc) : "a" (__pa(page_addr)), "d" (xpage_index) : "cc");
1028c2ecf20Sopenharmony_ci	if (cc == 3)
1038c2ecf20Sopenharmony_ci		return -ENXIO;
1048c2ecf20Sopenharmony_ci	if (cc == 2)
1058c2ecf20Sopenharmony_ci		return -ENXIO;
1068c2ecf20Sopenharmony_ci	if (cc == 1)
1078c2ecf20Sopenharmony_ci		return -EIO;
1088c2ecf20Sopenharmony_ci	return 0;
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci/*
1128c2ecf20Sopenharmony_ci * Copy a 4kB page of main memory to an expanded memory page
1138c2ecf20Sopenharmony_ci * Arguments
1148c2ecf20Sopenharmony_ci *           page_addr:    address of source page
1158c2ecf20Sopenharmony_ci *           xpage_index:  index of expandeded memory page
1168c2ecf20Sopenharmony_ci * Return value
1178c2ecf20Sopenharmony_ci *           0:            if operation succeeds
1188c2ecf20Sopenharmony_ci *           -EIO:         if pgout failed
1198c2ecf20Sopenharmony_ci *           -ENXIO:       if xpram has vanished
1208c2ecf20Sopenharmony_ci */
1218c2ecf20Sopenharmony_cistatic long xpram_page_out (unsigned long page_addr, unsigned int xpage_index)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	int cc = 2;	/* return unused cc 2 if pgin traps */
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	asm volatile(
1268c2ecf20Sopenharmony_ci		"	.insn	rre,0xb22f0000,%1,%2\n"  /* pgout %1,%2 */
1278c2ecf20Sopenharmony_ci		"0:	ipm	%0\n"
1288c2ecf20Sopenharmony_ci		"	srl	%0,28\n"
1298c2ecf20Sopenharmony_ci		"1:\n"
1308c2ecf20Sopenharmony_ci		EX_TABLE(0b,1b)
1318c2ecf20Sopenharmony_ci		: "+d" (cc) : "a" (__pa(page_addr)), "d" (xpage_index) : "cc");
1328c2ecf20Sopenharmony_ci	if (cc == 3)
1338c2ecf20Sopenharmony_ci		return -ENXIO;
1348c2ecf20Sopenharmony_ci	if (cc == 2)
1358c2ecf20Sopenharmony_ci		return -ENXIO;
1368c2ecf20Sopenharmony_ci	if (cc == 1)
1378c2ecf20Sopenharmony_ci		return -EIO;
1388c2ecf20Sopenharmony_ci	return 0;
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci/*
1428c2ecf20Sopenharmony_ci * Check if xpram is available.
1438c2ecf20Sopenharmony_ci */
1448c2ecf20Sopenharmony_cistatic int xpram_present(void)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	unsigned long mem_page;
1478c2ecf20Sopenharmony_ci	int rc;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
1508c2ecf20Sopenharmony_ci	if (!mem_page)
1518c2ecf20Sopenharmony_ci		return -ENOMEM;
1528c2ecf20Sopenharmony_ci	rc = xpram_page_in(mem_page, 0);
1538c2ecf20Sopenharmony_ci	free_page(mem_page);
1548c2ecf20Sopenharmony_ci	return rc ? -ENXIO : 0;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci/*
1588c2ecf20Sopenharmony_ci * Return index of the last available xpram page.
1598c2ecf20Sopenharmony_ci */
1608c2ecf20Sopenharmony_cistatic unsigned long xpram_highest_page_index(void)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	unsigned int page_index, add_bit;
1638c2ecf20Sopenharmony_ci	unsigned long mem_page;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
1668c2ecf20Sopenharmony_ci	if (!mem_page)
1678c2ecf20Sopenharmony_ci		return 0;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	page_index = 0;
1708c2ecf20Sopenharmony_ci	add_bit = 1ULL << (sizeof(unsigned int)*8 - 1);
1718c2ecf20Sopenharmony_ci	while (add_bit > 0) {
1728c2ecf20Sopenharmony_ci		if (xpram_page_in(mem_page, page_index | add_bit) == 0)
1738c2ecf20Sopenharmony_ci			page_index |= add_bit;
1748c2ecf20Sopenharmony_ci		add_bit >>= 1;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	free_page (mem_page);
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	return page_index;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci/*
1838c2ecf20Sopenharmony_ci * Block device make request function.
1848c2ecf20Sopenharmony_ci */
1858c2ecf20Sopenharmony_cistatic blk_qc_t xpram_submit_bio(struct bio *bio)
1868c2ecf20Sopenharmony_ci{
1878c2ecf20Sopenharmony_ci	xpram_device_t *xdev = bio->bi_disk->private_data;
1888c2ecf20Sopenharmony_ci	struct bio_vec bvec;
1898c2ecf20Sopenharmony_ci	struct bvec_iter iter;
1908c2ecf20Sopenharmony_ci	unsigned int index;
1918c2ecf20Sopenharmony_ci	unsigned long page_addr;
1928c2ecf20Sopenharmony_ci	unsigned long bytes;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	blk_queue_split(&bio);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	if ((bio->bi_iter.bi_sector & 7) != 0 ||
1978c2ecf20Sopenharmony_ci	    (bio->bi_iter.bi_size & 4095) != 0)
1988c2ecf20Sopenharmony_ci		/* Request is not page-aligned. */
1998c2ecf20Sopenharmony_ci		goto fail;
2008c2ecf20Sopenharmony_ci	if ((bio->bi_iter.bi_size >> 12) > xdev->size)
2018c2ecf20Sopenharmony_ci		/* Request size is no page-aligned. */
2028c2ecf20Sopenharmony_ci		goto fail;
2038c2ecf20Sopenharmony_ci	if ((bio->bi_iter.bi_sector >> 3) > 0xffffffffU - xdev->offset)
2048c2ecf20Sopenharmony_ci		goto fail;
2058c2ecf20Sopenharmony_ci	index = (bio->bi_iter.bi_sector >> 3) + xdev->offset;
2068c2ecf20Sopenharmony_ci	bio_for_each_segment(bvec, bio, iter) {
2078c2ecf20Sopenharmony_ci		page_addr = (unsigned long)
2088c2ecf20Sopenharmony_ci			kmap(bvec.bv_page) + bvec.bv_offset;
2098c2ecf20Sopenharmony_ci		bytes = bvec.bv_len;
2108c2ecf20Sopenharmony_ci		if ((page_addr & 4095) != 0 || (bytes & 4095) != 0)
2118c2ecf20Sopenharmony_ci			/* More paranoia. */
2128c2ecf20Sopenharmony_ci			goto fail;
2138c2ecf20Sopenharmony_ci		while (bytes > 0) {
2148c2ecf20Sopenharmony_ci			if (bio_data_dir(bio) == READ) {
2158c2ecf20Sopenharmony_ci				if (xpram_page_in(page_addr, index) != 0)
2168c2ecf20Sopenharmony_ci					goto fail;
2178c2ecf20Sopenharmony_ci			} else {
2188c2ecf20Sopenharmony_ci				if (xpram_page_out(page_addr, index) != 0)
2198c2ecf20Sopenharmony_ci					goto fail;
2208c2ecf20Sopenharmony_ci			}
2218c2ecf20Sopenharmony_ci			page_addr += 4096;
2228c2ecf20Sopenharmony_ci			bytes -= 4096;
2238c2ecf20Sopenharmony_ci			index++;
2248c2ecf20Sopenharmony_ci		}
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci	bio_endio(bio);
2278c2ecf20Sopenharmony_ci	return BLK_QC_T_NONE;
2288c2ecf20Sopenharmony_cifail:
2298c2ecf20Sopenharmony_ci	bio_io_error(bio);
2308c2ecf20Sopenharmony_ci	return BLK_QC_T_NONE;
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_cistatic int xpram_getgeo(struct block_device *bdev, struct hd_geometry *geo)
2348c2ecf20Sopenharmony_ci{
2358c2ecf20Sopenharmony_ci	unsigned long size;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	/*
2388c2ecf20Sopenharmony_ci	 * get geometry: we have to fake one...  trim the size to a
2398c2ecf20Sopenharmony_ci	 * multiple of 64 (32k): tell we have 16 sectors, 4 heads,
2408c2ecf20Sopenharmony_ci	 * whatever cylinders. Tell also that data starts at sector. 4.
2418c2ecf20Sopenharmony_ci	 */
2428c2ecf20Sopenharmony_ci	size = (xpram_pages * 8) & ~0x3f;
2438c2ecf20Sopenharmony_ci	geo->cylinders = size >> 6;
2448c2ecf20Sopenharmony_ci	geo->heads = 4;
2458c2ecf20Sopenharmony_ci	geo->sectors = 16;
2468c2ecf20Sopenharmony_ci	geo->start = 4;
2478c2ecf20Sopenharmony_ci	return 0;
2488c2ecf20Sopenharmony_ci}
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_cistatic const struct block_device_operations xpram_devops =
2518c2ecf20Sopenharmony_ci{
2528c2ecf20Sopenharmony_ci	.owner	= THIS_MODULE,
2538c2ecf20Sopenharmony_ci	.submit_bio = xpram_submit_bio,
2548c2ecf20Sopenharmony_ci	.getgeo	= xpram_getgeo,
2558c2ecf20Sopenharmony_ci};
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci/*
2588c2ecf20Sopenharmony_ci * Setup xpram_sizes array.
2598c2ecf20Sopenharmony_ci */
2608c2ecf20Sopenharmony_cistatic int __init xpram_setup_sizes(unsigned long pages)
2618c2ecf20Sopenharmony_ci{
2628c2ecf20Sopenharmony_ci	unsigned long mem_needed;
2638c2ecf20Sopenharmony_ci	unsigned long mem_auto;
2648c2ecf20Sopenharmony_ci	unsigned long long size;
2658c2ecf20Sopenharmony_ci	char *sizes_end;
2668c2ecf20Sopenharmony_ci	int mem_auto_no;
2678c2ecf20Sopenharmony_ci	int i;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	/* Check number of devices. */
2708c2ecf20Sopenharmony_ci	if (devs <= 0 || devs > XPRAM_MAX_DEVS) {
2718c2ecf20Sopenharmony_ci		pr_err("%d is not a valid number of XPRAM devices\n",devs);
2728c2ecf20Sopenharmony_ci		return -EINVAL;
2738c2ecf20Sopenharmony_ci	}
2748c2ecf20Sopenharmony_ci	xpram_devs = devs;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	/*
2778c2ecf20Sopenharmony_ci	 * Copy sizes array to xpram_sizes and align partition
2788c2ecf20Sopenharmony_ci	 * sizes to page boundary.
2798c2ecf20Sopenharmony_ci	 */
2808c2ecf20Sopenharmony_ci	mem_needed = 0;
2818c2ecf20Sopenharmony_ci	mem_auto_no = 0;
2828c2ecf20Sopenharmony_ci	for (i = 0; i < xpram_devs; i++) {
2838c2ecf20Sopenharmony_ci		if (sizes[i]) {
2848c2ecf20Sopenharmony_ci			size = simple_strtoull(sizes[i], &sizes_end, 0);
2858c2ecf20Sopenharmony_ci			switch (*sizes_end) {
2868c2ecf20Sopenharmony_ci			case 'g':
2878c2ecf20Sopenharmony_ci			case 'G':
2888c2ecf20Sopenharmony_ci				size <<= 20;
2898c2ecf20Sopenharmony_ci				break;
2908c2ecf20Sopenharmony_ci			case 'm':
2918c2ecf20Sopenharmony_ci			case 'M':
2928c2ecf20Sopenharmony_ci				size <<= 10;
2938c2ecf20Sopenharmony_ci			}
2948c2ecf20Sopenharmony_ci			xpram_sizes[i] = (size + 3) & -4UL;
2958c2ecf20Sopenharmony_ci		}
2968c2ecf20Sopenharmony_ci		if (xpram_sizes[i])
2978c2ecf20Sopenharmony_ci			mem_needed += xpram_sizes[i];
2988c2ecf20Sopenharmony_ci		else
2998c2ecf20Sopenharmony_ci			mem_auto_no++;
3008c2ecf20Sopenharmony_ci	}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	pr_info("  number of devices (partitions): %d \n", xpram_devs);
3038c2ecf20Sopenharmony_ci	for (i = 0; i < xpram_devs; i++) {
3048c2ecf20Sopenharmony_ci		if (xpram_sizes[i])
3058c2ecf20Sopenharmony_ci			pr_info("  size of partition %d: %u kB\n",
3068c2ecf20Sopenharmony_ci				i, xpram_sizes[i]);
3078c2ecf20Sopenharmony_ci		else
3088c2ecf20Sopenharmony_ci			pr_info("  size of partition %d to be set "
3098c2ecf20Sopenharmony_ci				"automatically\n",i);
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci	pr_info("  memory needed (for sized partitions): %lu kB\n",
3128c2ecf20Sopenharmony_ci		mem_needed);
3138c2ecf20Sopenharmony_ci	pr_info("  partitions to be sized automatically: %d\n",
3148c2ecf20Sopenharmony_ci		mem_auto_no);
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	if (mem_needed > pages * 4) {
3178c2ecf20Sopenharmony_ci		pr_err("Not enough expanded memory available\n");
3188c2ecf20Sopenharmony_ci		return -EINVAL;
3198c2ecf20Sopenharmony_ci	}
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	/*
3228c2ecf20Sopenharmony_ci	 * partitioning:
3238c2ecf20Sopenharmony_ci	 * xpram_sizes[i] != 0; partition i has size xpram_sizes[i] kB
3248c2ecf20Sopenharmony_ci	 * else:             ; all partitions with zero xpram_sizes[i]
3258c2ecf20Sopenharmony_ci	 *                     partition equally the remaining space
3268c2ecf20Sopenharmony_ci	 */
3278c2ecf20Sopenharmony_ci	if (mem_auto_no) {
3288c2ecf20Sopenharmony_ci		mem_auto = ((pages - mem_needed / 4) / mem_auto_no) * 4;
3298c2ecf20Sopenharmony_ci		pr_info("  automatically determined "
3308c2ecf20Sopenharmony_ci			"partition size: %lu kB\n", mem_auto);
3318c2ecf20Sopenharmony_ci		for (i = 0; i < xpram_devs; i++)
3328c2ecf20Sopenharmony_ci			if (xpram_sizes[i] == 0)
3338c2ecf20Sopenharmony_ci				xpram_sizes[i] = mem_auto;
3348c2ecf20Sopenharmony_ci	}
3358c2ecf20Sopenharmony_ci	return 0;
3368c2ecf20Sopenharmony_ci}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_cistatic int __init xpram_setup_blkdev(void)
3398c2ecf20Sopenharmony_ci{
3408c2ecf20Sopenharmony_ci	unsigned long offset;
3418c2ecf20Sopenharmony_ci	int i, rc = -ENOMEM;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	for (i = 0; i < xpram_devs; i++) {
3448c2ecf20Sopenharmony_ci		xpram_disks[i] = alloc_disk(1);
3458c2ecf20Sopenharmony_ci		if (!xpram_disks[i])
3468c2ecf20Sopenharmony_ci			goto out;
3478c2ecf20Sopenharmony_ci		xpram_queues[i] = blk_alloc_queue(NUMA_NO_NODE);
3488c2ecf20Sopenharmony_ci		if (!xpram_queues[i]) {
3498c2ecf20Sopenharmony_ci			put_disk(xpram_disks[i]);
3508c2ecf20Sopenharmony_ci			goto out;
3518c2ecf20Sopenharmony_ci		}
3528c2ecf20Sopenharmony_ci		blk_queue_flag_set(QUEUE_FLAG_NONROT, xpram_queues[i]);
3538c2ecf20Sopenharmony_ci		blk_queue_flag_clear(QUEUE_FLAG_ADD_RANDOM, xpram_queues[i]);
3548c2ecf20Sopenharmony_ci		blk_queue_logical_block_size(xpram_queues[i], 4096);
3558c2ecf20Sopenharmony_ci	}
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	/*
3588c2ecf20Sopenharmony_ci	 * Register xpram major.
3598c2ecf20Sopenharmony_ci	 */
3608c2ecf20Sopenharmony_ci	rc = register_blkdev(XPRAM_MAJOR, XPRAM_NAME);
3618c2ecf20Sopenharmony_ci	if (rc < 0)
3628c2ecf20Sopenharmony_ci		goto out;
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	/*
3658c2ecf20Sopenharmony_ci	 * Setup device structures.
3668c2ecf20Sopenharmony_ci	 */
3678c2ecf20Sopenharmony_ci	offset = 0;
3688c2ecf20Sopenharmony_ci	for (i = 0; i < xpram_devs; i++) {
3698c2ecf20Sopenharmony_ci		struct gendisk *disk = xpram_disks[i];
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci		xpram_devices[i].size = xpram_sizes[i] / 4;
3728c2ecf20Sopenharmony_ci		xpram_devices[i].offset = offset;
3738c2ecf20Sopenharmony_ci		offset += xpram_devices[i].size;
3748c2ecf20Sopenharmony_ci		disk->major = XPRAM_MAJOR;
3758c2ecf20Sopenharmony_ci		disk->first_minor = i;
3768c2ecf20Sopenharmony_ci		disk->fops = &xpram_devops;
3778c2ecf20Sopenharmony_ci		disk->private_data = &xpram_devices[i];
3788c2ecf20Sopenharmony_ci		disk->queue = xpram_queues[i];
3798c2ecf20Sopenharmony_ci		sprintf(disk->disk_name, "slram%d", i);
3808c2ecf20Sopenharmony_ci		set_capacity(disk, xpram_sizes[i] << 1);
3818c2ecf20Sopenharmony_ci		add_disk(disk);
3828c2ecf20Sopenharmony_ci	}
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci	return 0;
3858c2ecf20Sopenharmony_ciout:
3868c2ecf20Sopenharmony_ci	while (i--) {
3878c2ecf20Sopenharmony_ci		blk_cleanup_queue(xpram_queues[i]);
3888c2ecf20Sopenharmony_ci		put_disk(xpram_disks[i]);
3898c2ecf20Sopenharmony_ci	}
3908c2ecf20Sopenharmony_ci	return rc;
3918c2ecf20Sopenharmony_ci}
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci/*
3948c2ecf20Sopenharmony_ci * Resume failed: Print error message and call panic.
3958c2ecf20Sopenharmony_ci */
3968c2ecf20Sopenharmony_cistatic void xpram_resume_error(const char *message)
3978c2ecf20Sopenharmony_ci{
3988c2ecf20Sopenharmony_ci	pr_err("Resuming the system failed: %s\n", message);
3998c2ecf20Sopenharmony_ci	panic("xpram resume error\n");
4008c2ecf20Sopenharmony_ci}
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci/*
4038c2ecf20Sopenharmony_ci * Check if xpram setup changed between suspend and resume.
4048c2ecf20Sopenharmony_ci */
4058c2ecf20Sopenharmony_cistatic int xpram_restore(struct device *dev)
4068c2ecf20Sopenharmony_ci{
4078c2ecf20Sopenharmony_ci	if (!xpram_pages)
4088c2ecf20Sopenharmony_ci		return 0;
4098c2ecf20Sopenharmony_ci	if (xpram_present() != 0)
4108c2ecf20Sopenharmony_ci		xpram_resume_error("xpram disappeared");
4118c2ecf20Sopenharmony_ci	if (xpram_pages != xpram_highest_page_index() + 1)
4128c2ecf20Sopenharmony_ci		xpram_resume_error("Size of xpram changed");
4138c2ecf20Sopenharmony_ci	return 0;
4148c2ecf20Sopenharmony_ci}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_cistatic const struct dev_pm_ops xpram_pm_ops = {
4178c2ecf20Sopenharmony_ci	.restore	= xpram_restore,
4188c2ecf20Sopenharmony_ci};
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_cistatic struct platform_driver xpram_pdrv = {
4218c2ecf20Sopenharmony_ci	.driver = {
4228c2ecf20Sopenharmony_ci		.name	= XPRAM_NAME,
4238c2ecf20Sopenharmony_ci		.pm	= &xpram_pm_ops,
4248c2ecf20Sopenharmony_ci	},
4258c2ecf20Sopenharmony_ci};
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_cistatic struct platform_device *xpram_pdev;
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci/*
4308c2ecf20Sopenharmony_ci * Finally, the init/exit functions.
4318c2ecf20Sopenharmony_ci */
4328c2ecf20Sopenharmony_cistatic void __exit xpram_exit(void)
4338c2ecf20Sopenharmony_ci{
4348c2ecf20Sopenharmony_ci	int i;
4358c2ecf20Sopenharmony_ci	for (i = 0; i < xpram_devs; i++) {
4368c2ecf20Sopenharmony_ci		del_gendisk(xpram_disks[i]);
4378c2ecf20Sopenharmony_ci		blk_cleanup_queue(xpram_queues[i]);
4388c2ecf20Sopenharmony_ci		put_disk(xpram_disks[i]);
4398c2ecf20Sopenharmony_ci	}
4408c2ecf20Sopenharmony_ci	unregister_blkdev(XPRAM_MAJOR, XPRAM_NAME);
4418c2ecf20Sopenharmony_ci	platform_device_unregister(xpram_pdev);
4428c2ecf20Sopenharmony_ci	platform_driver_unregister(&xpram_pdrv);
4438c2ecf20Sopenharmony_ci}
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_cistatic int __init xpram_init(void)
4468c2ecf20Sopenharmony_ci{
4478c2ecf20Sopenharmony_ci	int rc;
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci	/* Find out size of expanded memory. */
4508c2ecf20Sopenharmony_ci	if (xpram_present() != 0) {
4518c2ecf20Sopenharmony_ci		pr_err("No expanded memory available\n");
4528c2ecf20Sopenharmony_ci		return -ENODEV;
4538c2ecf20Sopenharmony_ci	}
4548c2ecf20Sopenharmony_ci	xpram_pages = xpram_highest_page_index() + 1;
4558c2ecf20Sopenharmony_ci	pr_info("  %u pages expanded memory found (%lu KB).\n",
4568c2ecf20Sopenharmony_ci		xpram_pages, (unsigned long) xpram_pages*4);
4578c2ecf20Sopenharmony_ci	rc = xpram_setup_sizes(xpram_pages);
4588c2ecf20Sopenharmony_ci	if (rc)
4598c2ecf20Sopenharmony_ci		return rc;
4608c2ecf20Sopenharmony_ci	rc = platform_driver_register(&xpram_pdrv);
4618c2ecf20Sopenharmony_ci	if (rc)
4628c2ecf20Sopenharmony_ci		return rc;
4638c2ecf20Sopenharmony_ci	xpram_pdev = platform_device_register_simple(XPRAM_NAME, -1, NULL, 0);
4648c2ecf20Sopenharmony_ci	if (IS_ERR(xpram_pdev)) {
4658c2ecf20Sopenharmony_ci		rc = PTR_ERR(xpram_pdev);
4668c2ecf20Sopenharmony_ci		goto fail_platform_driver_unregister;
4678c2ecf20Sopenharmony_ci	}
4688c2ecf20Sopenharmony_ci	rc = xpram_setup_blkdev();
4698c2ecf20Sopenharmony_ci	if (rc)
4708c2ecf20Sopenharmony_ci		goto fail_platform_device_unregister;
4718c2ecf20Sopenharmony_ci	return 0;
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_cifail_platform_device_unregister:
4748c2ecf20Sopenharmony_ci	platform_device_unregister(xpram_pdev);
4758c2ecf20Sopenharmony_cifail_platform_driver_unregister:
4768c2ecf20Sopenharmony_ci	platform_driver_unregister(&xpram_pdrv);
4778c2ecf20Sopenharmony_ci	return rc;
4788c2ecf20Sopenharmony_ci}
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_cimodule_init(xpram_init);
4818c2ecf20Sopenharmony_cimodule_exit(xpram_exit);
482