1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* flash.c: Allow mmap access to the OBP Flash, for OBP updates. |
3 | * |
4 | * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) |
5 | */ |
6 | |
7 | #include <linux/module.h> |
8 | #include <linux/types.h> |
9 | #include <linux/errno.h> |
10 | #include <linux/miscdevice.h> |
11 | #include <linux/fcntl.h> |
12 | #include <linux/poll.h> |
13 | #include <linux/mutex.h> |
14 | #include <linux/spinlock.h> |
15 | #include <linux/mm.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | |
19 | #include <linux/uaccess.h> |
20 | #include <asm/io.h> |
21 | #include <asm/upa.h> |
22 | |
23 | static DEFINE_MUTEX(flash_mutex); |
24 | static DEFINE_SPINLOCK(flash_lock); |
25 | static struct { |
26 | unsigned long read_base; /* Physical read address */ |
27 | unsigned long write_base; /* Physical write address */ |
28 | unsigned long read_size; /* Size of read area */ |
29 | unsigned long write_size; /* Size of write area */ |
30 | unsigned long busy; /* In use? */ |
31 | } flash; |
32 | |
33 | static int |
34 | flash_mmap(struct file *file, struct vm_area_struct *vma) |
35 | { |
36 | unsigned long addr; |
37 | unsigned long size; |
38 | |
39 | spin_lock(lock: &flash_lock); |
40 | if (flash.read_base == flash.write_base) { |
41 | addr = flash.read_base; |
42 | size = flash.read_size; |
43 | } else { |
44 | if ((vma->vm_flags & VM_READ) && |
45 | (vma->vm_flags & VM_WRITE)) { |
46 | spin_unlock(lock: &flash_lock); |
47 | return -EINVAL; |
48 | } |
49 | if (vma->vm_flags & VM_READ) { |
50 | addr = flash.read_base; |
51 | size = flash.read_size; |
52 | } else if (vma->vm_flags & VM_WRITE) { |
53 | addr = flash.write_base; |
54 | size = flash.write_size; |
55 | } else { |
56 | spin_unlock(lock: &flash_lock); |
57 | return -ENXIO; |
58 | } |
59 | } |
60 | spin_unlock(lock: &flash_lock); |
61 | |
62 | if ((vma->vm_pgoff << PAGE_SHIFT) > size) |
63 | return -ENXIO; |
64 | addr = vma->vm_pgoff + (addr >> PAGE_SHIFT); |
65 | |
66 | if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size) |
67 | size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)); |
68 | |
69 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
70 | |
71 | if (io_remap_pfn_range(vma, addr: vma->vm_start, pfn: addr, size, prot: vma->vm_page_prot)) |
72 | return -EAGAIN; |
73 | |
74 | return 0; |
75 | } |
76 | |
77 | static long long |
78 | flash_llseek(struct file *file, long long offset, int origin) |
79 | { |
80 | mutex_lock(&flash_mutex); |
81 | switch (origin) { |
82 | case 0: |
83 | file->f_pos = offset; |
84 | break; |
85 | case 1: |
86 | file->f_pos += offset; |
87 | if (file->f_pos > flash.read_size) |
88 | file->f_pos = flash.read_size; |
89 | break; |
90 | case 2: |
91 | file->f_pos = flash.read_size; |
92 | break; |
93 | default: |
94 | mutex_unlock(lock: &flash_mutex); |
95 | return -EINVAL; |
96 | } |
97 | mutex_unlock(lock: &flash_mutex); |
98 | return file->f_pos; |
99 | } |
100 | |
101 | static ssize_t |
102 | flash_read(struct file * file, char __user * buf, |
103 | size_t count, loff_t *ppos) |
104 | { |
105 | loff_t p = *ppos; |
106 | int i; |
107 | |
108 | if (count > flash.read_size - p) |
109 | count = flash.read_size - p; |
110 | |
111 | for (i = 0; i < count; i++) { |
112 | u8 data = upa_readb(flash.read_base + p + i); |
113 | if (put_user(data, buf)) |
114 | return -EFAULT; |
115 | buf++; |
116 | } |
117 | |
118 | *ppos += count; |
119 | return count; |
120 | } |
121 | |
122 | static int |
123 | flash_open(struct inode *inode, struct file *file) |
124 | { |
125 | mutex_lock(&flash_mutex); |
126 | if (test_and_set_bit(nr: 0, addr: (void *)&flash.busy) != 0) { |
127 | mutex_unlock(lock: &flash_mutex); |
128 | return -EBUSY; |
129 | } |
130 | |
131 | mutex_unlock(lock: &flash_mutex); |
132 | return 0; |
133 | } |
134 | |
135 | static int |
136 | flash_release(struct inode *inode, struct file *file) |
137 | { |
138 | spin_lock(lock: &flash_lock); |
139 | flash.busy = 0; |
140 | spin_unlock(lock: &flash_lock); |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static const struct file_operations flash_fops = { |
146 | /* no write to the Flash, use mmap |
147 | * and play flash dependent tricks. |
148 | */ |
149 | .owner = THIS_MODULE, |
150 | .llseek = flash_llseek, |
151 | .read = flash_read, |
152 | .mmap = flash_mmap, |
153 | .open = flash_open, |
154 | .release = flash_release, |
155 | }; |
156 | |
157 | static struct miscdevice flash_dev = { SBUS_FLASH_MINOR, "flash" , &flash_fops }; |
158 | |
159 | static int flash_probe(struct platform_device *op) |
160 | { |
161 | struct device_node *dp = op->dev.of_node; |
162 | struct device_node *parent; |
163 | |
164 | parent = dp->parent; |
165 | |
166 | if (!of_node_name_eq(np: parent, name: "sbus" ) && |
167 | !of_node_name_eq(np: parent, name: "sbi" ) && |
168 | !of_node_name_eq(np: parent, name: "ebus" )) |
169 | return -ENODEV; |
170 | |
171 | flash.read_base = op->resource[0].start; |
172 | flash.read_size = resource_size(res: &op->resource[0]); |
173 | if (op->resource[1].flags) { |
174 | flash.write_base = op->resource[1].start; |
175 | flash.write_size = resource_size(res: &op->resource[1]); |
176 | } else { |
177 | flash.write_base = op->resource[0].start; |
178 | flash.write_size = resource_size(res: &op->resource[0]); |
179 | } |
180 | flash.busy = 0; |
181 | |
182 | printk(KERN_INFO "%pOF: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n" , |
183 | op->dev.of_node, |
184 | flash.read_base, flash.read_size, |
185 | flash.write_base, flash.write_size); |
186 | |
187 | return misc_register(misc: &flash_dev); |
188 | } |
189 | |
190 | static void flash_remove(struct platform_device *op) |
191 | { |
192 | misc_deregister(misc: &flash_dev); |
193 | } |
194 | |
195 | static const struct of_device_id flash_match[] = { |
196 | { |
197 | .name = "flashprom" , |
198 | }, |
199 | {}, |
200 | }; |
201 | MODULE_DEVICE_TABLE(of, flash_match); |
202 | |
203 | static struct platform_driver flash_driver = { |
204 | .driver = { |
205 | .name = "flash" , |
206 | .of_match_table = flash_match, |
207 | }, |
208 | .probe = flash_probe, |
209 | .remove_new = flash_remove, |
210 | }; |
211 | |
212 | module_platform_driver(flash_driver); |
213 | |
214 | MODULE_LICENSE("GPL" ); |
215 | |