diff options
-rw-r--r-- | Documentation/kernel-parameters.txt | 6 | ||||
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | arch/x86/Kconfig | 10 | ||||
-rw-r--r-- | arch/x86/include/uapi/asm/e820.h | 10 | ||||
-rw-r--r-- | arch/x86/kernel/Makefile | 1 | ||||
-rw-r--r-- | arch/x86/kernel/e820.c | 26 | ||||
-rw-r--r-- | arch/x86/kernel/pmem.c | 53 | ||||
-rw-r--r-- | drivers/block/Kconfig | 11 | ||||
-rw-r--r-- | drivers/block/Makefile | 1 | ||||
-rw-r--r-- | drivers/block/pmem.c | 262 |
10 files changed, 380 insertions, 6 deletions
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 5bc92d2f4716..274252f205b7 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt | |||
@@ -1972,6 +1972,12 @@ bytes respectively. Such letter suffixes can also be entirely omitted. | |||
1972 | or | 1972 | or |
1973 | memmap=0x10000$0x18690000 | 1973 | memmap=0x10000$0x18690000 |
1974 | 1974 | ||
1975 | memmap=nn[KMG]!ss[KMG] | ||
1976 | [KNL,X86] Mark specific memory as protected. | ||
1977 | Region of memory to be used, from ss to ss+nn. | ||
1978 | The memory region may be marked as e820 type 12 (0xc) | ||
1979 | and is NVDIMM or ADR memory. | ||
1980 | |||
1975 | memory_corruption_check=0/1 [X86] | 1981 | memory_corruption_check=0/1 [X86] |
1976 | Some BIOSes seem to corrupt the first 64k of | 1982 | Some BIOSes seem to corrupt the first 64k of |
1977 | memory when doing things like suspend/resume. | 1983 | memory when doing things like suspend/resume. |
diff --git a/MAINTAINERS b/MAINTAINERS index dcd43465eae1..db335f98cad0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -8130,6 +8130,12 @@ S: Maintained | |||
8130 | F: Documentation/blockdev/ramdisk.txt | 8130 | F: Documentation/blockdev/ramdisk.txt |
8131 | F: drivers/block/brd.c | 8131 | F: drivers/block/brd.c |
8132 | 8132 | ||
8133 | PERSISTENT MEMORY DRIVER | ||
8134 | M: Ross Zwisler <ross.zwisler@linux.intel.com> | ||
8135 | L: linux-nvdimm@lists.01.org | ||
8136 | S: Supported | ||
8137 | F: drivers/block/pmem.c | ||
8138 | |||
8133 | RANDOM NUMBER DRIVER | 8139 | RANDOM NUMBER DRIVER |
8134 | M: "Theodore Ts'o" <tytso@mit.edu> | 8140 | M: "Theodore Ts'o" <tytso@mit.edu> |
8135 | S: Maintained | 8141 | S: Maintained |
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 937812b8e0b9..6049d587599e 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig | |||
@@ -1421,6 +1421,16 @@ config ILLEGAL_POINTER_VALUE | |||
1421 | 1421 | ||
1422 | source "mm/Kconfig" | 1422 | source "mm/Kconfig" |
1423 | 1423 | ||
1424 | config X86_PMEM_LEGACY | ||
1425 | bool "Support non-standard NVDIMMs and ADR protected memory" | ||
1426 | help | ||
1427 | Treat memory marked using the non-standard e820 type of 12 as used | ||
1428 | by the Intel Sandy Bridge-EP reference BIOS as protected memory. | ||
1429 | The kernel will offer these regions to the 'pmem' driver so | ||
1430 | they can be used for persistent storage. | ||
1431 | |||
1432 | Say Y if unsure. | ||
1433 | |||
1424 | config HIGHPTE | 1434 | config HIGHPTE |
1425 | bool "Allocate 3rd-level pagetables from highmem" | 1435 | bool "Allocate 3rd-level pagetables from highmem" |
1426 | depends on HIGHMEM | 1436 | depends on HIGHMEM |
diff --git a/arch/x86/include/uapi/asm/e820.h b/arch/x86/include/uapi/asm/e820.h index d993e33f5236..960a8a9dc4ab 100644 --- a/arch/x86/include/uapi/asm/e820.h +++ b/arch/x86/include/uapi/asm/e820.h | |||
@@ -33,6 +33,16 @@ | |||
33 | #define E820_NVS 4 | 33 | #define E820_NVS 4 |
34 | #define E820_UNUSABLE 5 | 34 | #define E820_UNUSABLE 5 |
35 | 35 | ||
36 | /* | ||
37 | * This is a non-standardized way to represent ADR or NVDIMM regions that | ||
38 | * persist over a reboot. The kernel will ignore their special capabilities | ||
39 | * unless the CONFIG_X86_PMEM_LEGACY=y option is set. | ||
40 | * | ||
41 | * ( Note that older platforms also used 6 for the same type of memory, | ||
42 | * but newer versions switched to 12 as 6 was assigned differently. Some | ||
43 | * time they will learn... ) | ||
44 | */ | ||
45 | #define E820_PRAM 12 | ||
36 | 46 | ||
37 | /* | 47 | /* |
38 | * reserved RAM used by kernel itself | 48 | * reserved RAM used by kernel itself |
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index c887cd944f0c..9bcd0b56ca17 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile | |||
@@ -95,6 +95,7 @@ obj-$(CONFIG_KVM_GUEST) += kvm.o kvmclock.o | |||
95 | obj-$(CONFIG_PARAVIRT) += paravirt.o paravirt_patch_$(BITS).o | 95 | obj-$(CONFIG_PARAVIRT) += paravirt.o paravirt_patch_$(BITS).o |
96 | obj-$(CONFIG_PARAVIRT_SPINLOCKS)+= paravirt-spinlocks.o | 96 | obj-$(CONFIG_PARAVIRT_SPINLOCKS)+= paravirt-spinlocks.o |
97 | obj-$(CONFIG_PARAVIRT_CLOCK) += pvclock.o | 97 | obj-$(CONFIG_PARAVIRT_CLOCK) += pvclock.o |
98 | obj-$(CONFIG_X86_PMEM_LEGACY) += pmem.o | ||
98 | 99 | ||
99 | obj-$(CONFIG_PCSPKR_PLATFORM) += pcspeaker.o | 100 | obj-$(CONFIG_PCSPKR_PLATFORM) += pcspeaker.o |
100 | 101 | ||
diff --git a/arch/x86/kernel/e820.c b/arch/x86/kernel/e820.c index 7d46bb260334..e2ce85db2283 100644 --- a/arch/x86/kernel/e820.c +++ b/arch/x86/kernel/e820.c | |||
@@ -149,6 +149,9 @@ static void __init e820_print_type(u32 type) | |||
149 | case E820_UNUSABLE: | 149 | case E820_UNUSABLE: |
150 | printk(KERN_CONT "unusable"); | 150 | printk(KERN_CONT "unusable"); |
151 | break; | 151 | break; |
152 | case E820_PRAM: | ||
153 | printk(KERN_CONT "persistent (type %u)", type); | ||
154 | break; | ||
152 | default: | 155 | default: |
153 | printk(KERN_CONT "type %u", type); | 156 | printk(KERN_CONT "type %u", type); |
154 | break; | 157 | break; |
@@ -343,7 +346,7 @@ int __init sanitize_e820_map(struct e820entry *biosmap, int max_nr_map, | |||
343 | * continue building up new bios map based on this | 346 | * continue building up new bios map based on this |
344 | * information | 347 | * information |
345 | */ | 348 | */ |
346 | if (current_type != last_type) { | 349 | if (current_type != last_type || current_type == E820_PRAM) { |
347 | if (last_type != 0) { | 350 | if (last_type != 0) { |
348 | new_bios[new_bios_entry].size = | 351 | new_bios[new_bios_entry].size = |
349 | change_point[chgidx]->addr - last_addr; | 352 | change_point[chgidx]->addr - last_addr; |
@@ -688,6 +691,7 @@ void __init e820_mark_nosave_regions(unsigned long limit_pfn) | |||
688 | register_nosave_region(pfn, PFN_UP(ei->addr)); | 691 | register_nosave_region(pfn, PFN_UP(ei->addr)); |
689 | 692 | ||
690 | pfn = PFN_DOWN(ei->addr + ei->size); | 693 | pfn = PFN_DOWN(ei->addr + ei->size); |
694 | |||
691 | if (ei->type != E820_RAM && ei->type != E820_RESERVED_KERN) | 695 | if (ei->type != E820_RAM && ei->type != E820_RESERVED_KERN) |
692 | register_nosave_region(PFN_UP(ei->addr), pfn); | 696 | register_nosave_region(PFN_UP(ei->addr), pfn); |
693 | 697 | ||
@@ -748,7 +752,7 @@ u64 __init early_reserve_e820(u64 size, u64 align) | |||
748 | /* | 752 | /* |
749 | * Find the highest page frame number we have available | 753 | * Find the highest page frame number we have available |
750 | */ | 754 | */ |
751 | static unsigned long __init e820_end_pfn(unsigned long limit_pfn, unsigned type) | 755 | static unsigned long __init e820_end_pfn(unsigned long limit_pfn) |
752 | { | 756 | { |
753 | int i; | 757 | int i; |
754 | unsigned long last_pfn = 0; | 758 | unsigned long last_pfn = 0; |
@@ -759,7 +763,11 @@ static unsigned long __init e820_end_pfn(unsigned long limit_pfn, unsigned type) | |||
759 | unsigned long start_pfn; | 763 | unsigned long start_pfn; |
760 | unsigned long end_pfn; | 764 | unsigned long end_pfn; |
761 | 765 | ||
762 | if (ei->type != type) | 766 | /* |
767 | * Persistent memory is accounted as ram for purposes of | ||
768 | * establishing max_pfn and mem_map. | ||
769 | */ | ||
770 | if (ei->type != E820_RAM && ei->type != E820_PRAM) | ||
763 | continue; | 771 | continue; |
764 | 772 | ||
765 | start_pfn = ei->addr >> PAGE_SHIFT; | 773 | start_pfn = ei->addr >> PAGE_SHIFT; |
@@ -784,12 +792,12 @@ static unsigned long __init e820_end_pfn(unsigned long limit_pfn, unsigned type) | |||
784 | } | 792 | } |
785 | unsigned long __init e820_end_of_ram_pfn(void) | 793 | unsigned long __init e820_end_of_ram_pfn(void) |
786 | { | 794 | { |
787 | return e820_end_pfn(MAX_ARCH_PFN, E820_RAM); | 795 | return e820_end_pfn(MAX_ARCH_PFN); |
788 | } | 796 | } |
789 | 797 | ||
790 | unsigned long __init e820_end_of_low_ram_pfn(void) | 798 | unsigned long __init e820_end_of_low_ram_pfn(void) |
791 | { | 799 | { |
792 | return e820_end_pfn(1UL<<(32 - PAGE_SHIFT), E820_RAM); | 800 | return e820_end_pfn(1UL << (32-PAGE_SHIFT)); |
793 | } | 801 | } |
794 | 802 | ||
795 | static void early_panic(char *msg) | 803 | static void early_panic(char *msg) |
@@ -866,6 +874,9 @@ static int __init parse_memmap_one(char *p) | |||
866 | } else if (*p == '$') { | 874 | } else if (*p == '$') { |
867 | start_at = memparse(p+1, &p); | 875 | start_at = memparse(p+1, &p); |
868 | e820_add_region(start_at, mem_size, E820_RESERVED); | 876 | e820_add_region(start_at, mem_size, E820_RESERVED); |
877 | } else if (*p == '!') { | ||
878 | start_at = memparse(p+1, &p); | ||
879 | e820_add_region(start_at, mem_size, E820_PRAM); | ||
869 | } else | 880 | } else |
870 | e820_remove_range(mem_size, ULLONG_MAX - mem_size, E820_RAM, 1); | 881 | e820_remove_range(mem_size, ULLONG_MAX - mem_size, E820_RAM, 1); |
871 | 882 | ||
@@ -907,6 +918,7 @@ static inline const char *e820_type_to_string(int e820_type) | |||
907 | case E820_ACPI: return "ACPI Tables"; | 918 | case E820_ACPI: return "ACPI Tables"; |
908 | case E820_NVS: return "ACPI Non-volatile Storage"; | 919 | case E820_NVS: return "ACPI Non-volatile Storage"; |
909 | case E820_UNUSABLE: return "Unusable memory"; | 920 | case E820_UNUSABLE: return "Unusable memory"; |
921 | case E820_PRAM: return "Persistent RAM"; | ||
910 | default: return "reserved"; | 922 | default: return "reserved"; |
911 | } | 923 | } |
912 | } | 924 | } |
@@ -940,7 +952,9 @@ void __init e820_reserve_resources(void) | |||
940 | * pci device BAR resource and insert them later in | 952 | * pci device BAR resource and insert them later in |
941 | * pcibios_resource_survey() | 953 | * pcibios_resource_survey() |
942 | */ | 954 | */ |
943 | if (e820.map[i].type != E820_RESERVED || res->start < (1ULL<<20)) { | 955 | if (((e820.map[i].type != E820_RESERVED) && |
956 | (e820.map[i].type != E820_PRAM)) || | ||
957 | res->start < (1ULL<<20)) { | ||
944 | res->flags |= IORESOURCE_BUSY; | 958 | res->flags |= IORESOURCE_BUSY; |
945 | insert_resource(&iomem_resource, res); | 959 | insert_resource(&iomem_resource, res); |
946 | } | 960 | } |
diff --git a/arch/x86/kernel/pmem.c b/arch/x86/kernel/pmem.c new file mode 100644 index 000000000000..3420c874ddc5 --- /dev/null +++ b/arch/x86/kernel/pmem.c | |||
@@ -0,0 +1,53 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2015, Christoph Hellwig. | ||
3 | */ | ||
4 | #include <linux/memblock.h> | ||
5 | #include <linux/platform_device.h> | ||
6 | #include <linux/slab.h> | ||
7 | #include <asm/e820.h> | ||
8 | #include <asm/page_types.h> | ||
9 | #include <asm/setup.h> | ||
10 | |||
11 | static __init void register_pmem_device(struct resource *res) | ||
12 | { | ||
13 | struct platform_device *pdev; | ||
14 | int error; | ||
15 | |||
16 | pdev = platform_device_alloc("pmem", PLATFORM_DEVID_AUTO); | ||
17 | if (!pdev) | ||
18 | return; | ||
19 | |||
20 | error = platform_device_add_resources(pdev, res, 1); | ||
21 | if (error) | ||
22 | goto out_put_pdev; | ||
23 | |||
24 | error = platform_device_add(pdev); | ||
25 | if (error) | ||
26 | goto out_put_pdev; | ||
27 | return; | ||
28 | |||
29 | out_put_pdev: | ||
30 | dev_warn(&pdev->dev, "failed to add 'pmem' (persistent memory) device!\n"); | ||
31 | platform_device_put(pdev); | ||
32 | } | ||
33 | |||
34 | static __init int register_pmem_devices(void) | ||
35 | { | ||
36 | int i; | ||
37 | |||
38 | for (i = 0; i < e820.nr_map; i++) { | ||
39 | struct e820entry *ei = &e820.map[i]; | ||
40 | |||
41 | if (ei->type == E820_PRAM) { | ||
42 | struct resource res = { | ||
43 | .flags = IORESOURCE_MEM, | ||
44 | .start = ei->addr, | ||
45 | .end = ei->addr + ei->size - 1, | ||
46 | }; | ||
47 | register_pmem_device(&res); | ||
48 | } | ||
49 | } | ||
50 | |||
51 | return 0; | ||
52 | } | ||
53 | device_initcall(register_pmem_devices); | ||
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index 1b8094d4d7af..eb1fed5bd516 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig | |||
@@ -404,6 +404,17 @@ config BLK_DEV_RAM_DAX | |||
404 | and will prevent RAM block device backing store memory from being | 404 | and will prevent RAM block device backing store memory from being |
405 | allocated from highmem (only a problem for highmem systems). | 405 | allocated from highmem (only a problem for highmem systems). |
406 | 406 | ||
407 | config BLK_DEV_PMEM | ||
408 | tristate "Persistent memory block device support" | ||
409 | help | ||
410 | Saying Y here will allow you to use a contiguous range of reserved | ||
411 | memory as one or more persistent block devices. | ||
412 | |||
413 | To compile this driver as a module, choose M here: the module will be | ||
414 | called 'pmem'. | ||
415 | |||
416 | If unsure, say N. | ||
417 | |||
407 | config CDROM_PKTCDVD | 418 | config CDROM_PKTCDVD |
408 | tristate "Packet writing on CD/DVD media" | 419 | tristate "Packet writing on CD/DVD media" |
409 | depends on !UML | 420 | depends on !UML |
diff --git a/drivers/block/Makefile b/drivers/block/Makefile index 02b688d1438d..9cc6c18a1c7e 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile | |||
@@ -14,6 +14,7 @@ obj-$(CONFIG_PS3_VRAM) += ps3vram.o | |||
14 | obj-$(CONFIG_ATARI_FLOPPY) += ataflop.o | 14 | obj-$(CONFIG_ATARI_FLOPPY) += ataflop.o |
15 | obj-$(CONFIG_AMIGA_Z2RAM) += z2ram.o | 15 | obj-$(CONFIG_AMIGA_Z2RAM) += z2ram.o |
16 | obj-$(CONFIG_BLK_DEV_RAM) += brd.o | 16 | obj-$(CONFIG_BLK_DEV_RAM) += brd.o |
17 | obj-$(CONFIG_BLK_DEV_PMEM) += pmem.o | ||
17 | obj-$(CONFIG_BLK_DEV_LOOP) += loop.o | 18 | obj-$(CONFIG_BLK_DEV_LOOP) += loop.o |
18 | obj-$(CONFIG_BLK_CPQ_DA) += cpqarray.o | 19 | obj-$(CONFIG_BLK_CPQ_DA) += cpqarray.o |
19 | obj-$(CONFIG_BLK_CPQ_CISS_DA) += cciss.o | 20 | obj-$(CONFIG_BLK_CPQ_CISS_DA) += cciss.o |
diff --git a/drivers/block/pmem.c b/drivers/block/pmem.c new file mode 100644 index 000000000000..eabf4a8d0085 --- /dev/null +++ b/drivers/block/pmem.c | |||
@@ -0,0 +1,262 @@ | |||
1 | /* | ||
2 | * Persistent Memory Driver | ||
3 | * | ||
4 | * Copyright (c) 2014, Intel Corporation. | ||
5 | * Copyright (c) 2015, Christoph Hellwig <hch@lst.de>. | ||
6 | * Copyright (c) 2015, Boaz Harrosh <boaz@plexistor.com>. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms and conditions of the GNU General Public License, | ||
10 | * version 2, as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
15 | * more details. | ||
16 | */ | ||
17 | |||
18 | #include <asm/cacheflush.h> | ||
19 | #include <linux/blkdev.h> | ||
20 | #include <linux/hdreg.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/moduleparam.h> | ||
25 | #include <linux/slab.h> | ||
26 | |||
27 | #define PMEM_MINORS 16 | ||
28 | |||
29 | struct pmem_device { | ||
30 | struct request_queue *pmem_queue; | ||
31 | struct gendisk *pmem_disk; | ||
32 | |||
33 | /* One contiguous memory region per device */ | ||
34 | phys_addr_t phys_addr; | ||
35 | void *virt_addr; | ||
36 | size_t size; | ||
37 | }; | ||
38 | |||
39 | static int pmem_major; | ||
40 | static atomic_t pmem_index; | ||
41 | |||
42 | static void pmem_do_bvec(struct pmem_device *pmem, struct page *page, | ||
43 | unsigned int len, unsigned int off, int rw, | ||
44 | sector_t sector) | ||
45 | { | ||
46 | void *mem = kmap_atomic(page); | ||
47 | size_t pmem_off = sector << 9; | ||
48 | |||
49 | if (rw == READ) { | ||
50 | memcpy(mem + off, pmem->virt_addr + pmem_off, len); | ||
51 | flush_dcache_page(page); | ||
52 | } else { | ||
53 | flush_dcache_page(page); | ||
54 | memcpy(pmem->virt_addr + pmem_off, mem + off, len); | ||
55 | } | ||
56 | |||
57 | kunmap_atomic(mem); | ||
58 | } | ||
59 | |||
60 | static void pmem_make_request(struct request_queue *q, struct bio *bio) | ||
61 | { | ||
62 | struct block_device *bdev = bio->bi_bdev; | ||
63 | struct pmem_device *pmem = bdev->bd_disk->private_data; | ||
64 | int rw; | ||
65 | struct bio_vec bvec; | ||
66 | sector_t sector; | ||
67 | struct bvec_iter iter; | ||
68 | int err = 0; | ||
69 | |||
70 | if (bio_end_sector(bio) > get_capacity(bdev->bd_disk)) { | ||
71 | err = -EIO; | ||
72 | goto out; | ||
73 | } | ||
74 | |||
75 | BUG_ON(bio->bi_rw & REQ_DISCARD); | ||
76 | |||
77 | rw = bio_data_dir(bio); | ||
78 | sector = bio->bi_iter.bi_sector; | ||
79 | bio_for_each_segment(bvec, bio, iter) { | ||
80 | pmem_do_bvec(pmem, bvec.bv_page, bvec.bv_len, bvec.bv_offset, | ||
81 | rw, sector); | ||
82 | sector += bvec.bv_len >> 9; | ||
83 | } | ||
84 | |||
85 | out: | ||
86 | bio_endio(bio, err); | ||
87 | } | ||
88 | |||
89 | static int pmem_rw_page(struct block_device *bdev, sector_t sector, | ||
90 | struct page *page, int rw) | ||
91 | { | ||
92 | struct pmem_device *pmem = bdev->bd_disk->private_data; | ||
93 | |||
94 | pmem_do_bvec(pmem, page, PAGE_CACHE_SIZE, 0, rw, sector); | ||
95 | page_endio(page, rw & WRITE, 0); | ||
96 | |||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | static long pmem_direct_access(struct block_device *bdev, sector_t sector, | ||
101 | void **kaddr, unsigned long *pfn, long size) | ||
102 | { | ||
103 | struct pmem_device *pmem = bdev->bd_disk->private_data; | ||
104 | size_t offset = sector << 9; | ||
105 | |||
106 | if (!pmem) | ||
107 | return -ENODEV; | ||
108 | |||
109 | *kaddr = pmem->virt_addr + offset; | ||
110 | *pfn = (pmem->phys_addr + offset) >> PAGE_SHIFT; | ||
111 | |||
112 | return pmem->size - offset; | ||
113 | } | ||
114 | |||
115 | static const struct block_device_operations pmem_fops = { | ||
116 | .owner = THIS_MODULE, | ||
117 | .rw_page = pmem_rw_page, | ||
118 | .direct_access = pmem_direct_access, | ||
119 | }; | ||
120 | |||
121 | static struct pmem_device *pmem_alloc(struct device *dev, struct resource *res) | ||
122 | { | ||
123 | struct pmem_device *pmem; | ||
124 | struct gendisk *disk; | ||
125 | int idx, err; | ||
126 | |||
127 | err = -ENOMEM; | ||
128 | pmem = kzalloc(sizeof(*pmem), GFP_KERNEL); | ||
129 | if (!pmem) | ||
130 | goto out; | ||
131 | |||
132 | pmem->phys_addr = res->start; | ||
133 | pmem->size = resource_size(res); | ||
134 | |||
135 | err = -EINVAL; | ||
136 | if (!request_mem_region(pmem->phys_addr, pmem->size, "pmem")) { | ||
137 | dev_warn(dev, "could not reserve region [0x%pa:0x%zx]\n", &pmem->phys_addr, pmem->size); | ||
138 | goto out_free_dev; | ||
139 | } | ||
140 | |||
141 | /* | ||
142 | * Map the memory as non-cachable, as we can't write back the contents | ||
143 | * of the CPU caches in case of a crash. | ||
144 | */ | ||
145 | err = -ENOMEM; | ||
146 | pmem->virt_addr = ioremap_nocache(pmem->phys_addr, pmem->size); | ||
147 | if (!pmem->virt_addr) | ||
148 | goto out_release_region; | ||
149 | |||
150 | pmem->pmem_queue = blk_alloc_queue(GFP_KERNEL); | ||
151 | if (!pmem->pmem_queue) | ||
152 | goto out_unmap; | ||
153 | |||
154 | blk_queue_make_request(pmem->pmem_queue, pmem_make_request); | ||
155 | blk_queue_max_hw_sectors(pmem->pmem_queue, 1024); | ||
156 | blk_queue_bounce_limit(pmem->pmem_queue, BLK_BOUNCE_ANY); | ||
157 | |||
158 | disk = alloc_disk(PMEM_MINORS); | ||
159 | if (!disk) | ||
160 | goto out_free_queue; | ||
161 | |||
162 | idx = atomic_inc_return(&pmem_index) - 1; | ||
163 | |||
164 | disk->major = pmem_major; | ||
165 | disk->first_minor = PMEM_MINORS * idx; | ||
166 | disk->fops = &pmem_fops; | ||
167 | disk->private_data = pmem; | ||
168 | disk->queue = pmem->pmem_queue; | ||
169 | disk->flags = GENHD_FL_EXT_DEVT; | ||
170 | sprintf(disk->disk_name, "pmem%d", idx); | ||
171 | disk->driverfs_dev = dev; | ||
172 | set_capacity(disk, pmem->size >> 9); | ||
173 | pmem->pmem_disk = disk; | ||
174 | |||
175 | add_disk(disk); | ||
176 | |||
177 | return pmem; | ||
178 | |||
179 | out_free_queue: | ||
180 | blk_cleanup_queue(pmem->pmem_queue); | ||
181 | out_unmap: | ||
182 | iounmap(pmem->virt_addr); | ||
183 | out_release_region: | ||
184 | release_mem_region(pmem->phys_addr, pmem->size); | ||
185 | out_free_dev: | ||
186 | kfree(pmem); | ||
187 | out: | ||
188 | return ERR_PTR(err); | ||
189 | } | ||
190 | |||
191 | static void pmem_free(struct pmem_device *pmem) | ||
192 | { | ||
193 | del_gendisk(pmem->pmem_disk); | ||
194 | put_disk(pmem->pmem_disk); | ||
195 | blk_cleanup_queue(pmem->pmem_queue); | ||
196 | iounmap(pmem->virt_addr); | ||
197 | release_mem_region(pmem->phys_addr, pmem->size); | ||
198 | kfree(pmem); | ||
199 | } | ||
200 | |||
201 | static int pmem_probe(struct platform_device *pdev) | ||
202 | { | ||
203 | struct pmem_device *pmem; | ||
204 | struct resource *res; | ||
205 | |||
206 | if (WARN_ON(pdev->num_resources > 1)) | ||
207 | return -ENXIO; | ||
208 | |||
209 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
210 | if (!res) | ||
211 | return -ENXIO; | ||
212 | |||
213 | pmem = pmem_alloc(&pdev->dev, res); | ||
214 | if (IS_ERR(pmem)) | ||
215 | return PTR_ERR(pmem); | ||
216 | |||
217 | platform_set_drvdata(pdev, pmem); | ||
218 | |||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | static int pmem_remove(struct platform_device *pdev) | ||
223 | { | ||
224 | struct pmem_device *pmem = platform_get_drvdata(pdev); | ||
225 | |||
226 | pmem_free(pmem); | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static struct platform_driver pmem_driver = { | ||
231 | .probe = pmem_probe, | ||
232 | .remove = pmem_remove, | ||
233 | .driver = { | ||
234 | .owner = THIS_MODULE, | ||
235 | .name = "pmem", | ||
236 | }, | ||
237 | }; | ||
238 | |||
239 | static int __init pmem_init(void) | ||
240 | { | ||
241 | int error; | ||
242 | |||
243 | pmem_major = register_blkdev(0, "pmem"); | ||
244 | if (pmem_major < 0) | ||
245 | return pmem_major; | ||
246 | |||
247 | error = platform_driver_register(&pmem_driver); | ||
248 | if (error) | ||
249 | unregister_blkdev(pmem_major, "pmem"); | ||
250 | return error; | ||
251 | } | ||
252 | module_init(pmem_init); | ||
253 | |||
254 | static void pmem_exit(void) | ||
255 | { | ||
256 | platform_driver_unregister(&pmem_driver); | ||
257 | unregister_blkdev(pmem_major, "pmem"); | ||
258 | } | ||
259 | module_exit(pmem_exit); | ||
260 | |||
261 | MODULE_AUTHOR("Ross Zwisler <ross.zwisler@linux.intel.com>"); | ||
262 | MODULE_LICENSE("GPL v2"); | ||