1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* IBM POWER Barrier Synchronization Register Driver |
3 | * |
4 | * Copyright IBM Corporation 2008 |
5 | * |
6 | * Author: Sonny Rao <sonnyrao@us.ibm.com> |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_address.h> |
13 | #include <linux/fs.h> |
14 | #include <linux/module.h> |
15 | #include <linux/cdev.h> |
16 | #include <linux/list.h> |
17 | #include <linux/mm.h> |
18 | #include <linux/slab.h> |
19 | #include <asm/io.h> |
20 | |
21 | /* |
22 | This driver exposes a special register which can be used for fast |
23 | synchronization across a large SMP machine. The hardware is exposed |
24 | as an array of bytes where each process will write to one of the bytes to |
25 | indicate it has finished the current stage and this update is broadcast to |
26 | all processors without having to bounce a cacheline between them. In |
27 | POWER5 and POWER6 there is one of these registers per SMP, but it is |
28 | presented in two forms; first, it is given as a whole and then as a number |
29 | of smaller registers which alias to parts of the single whole register. |
30 | This can potentially allow multiple groups of processes to each have their |
31 | own private synchronization device. |
32 | |
33 | Note that this hardware *must* be written to using *only* single byte writes. |
34 | It may be read using 1, 2, 4, or 8 byte loads which must be aligned since |
35 | this region is treated as cache-inhibited processes should also use a |
36 | full sync before and after writing to the BSR to ensure all stores and |
37 | the BSR update have made it to all chips in the system |
38 | */ |
39 | |
40 | /* This is arbitrary number, up to Power6 it's been 17 or fewer */ |
41 | #define BSR_MAX_DEVS (32) |
42 | |
43 | struct bsr_dev { |
44 | u64 bsr_addr; /* Real address */ |
45 | u64 bsr_len; /* length of mem region we can map */ |
46 | unsigned bsr_bytes; /* size of the BSR reg itself */ |
47 | unsigned bsr_stride; /* interval at which BSR repeats in the page */ |
48 | unsigned bsr_type; /* maps to enum below */ |
49 | unsigned bsr_num; /* bsr id number for its type */ |
50 | int bsr_minor; |
51 | |
52 | struct list_head bsr_list; |
53 | |
54 | dev_t bsr_dev; |
55 | struct cdev bsr_cdev; |
56 | struct device *bsr_device; |
57 | char bsr_name[32]; |
58 | |
59 | }; |
60 | |
61 | static unsigned total_bsr_devs; |
62 | static LIST_HEAD(bsr_devs); |
63 | static int bsr_major; |
64 | |
65 | enum { |
66 | BSR_8 = 0, |
67 | BSR_16 = 1, |
68 | BSR_64 = 2, |
69 | BSR_128 = 3, |
70 | BSR_4096 = 4, |
71 | BSR_UNKNOWN = 5, |
72 | BSR_MAX = 6, |
73 | }; |
74 | |
75 | static unsigned bsr_types[BSR_MAX]; |
76 | |
77 | static ssize_t |
78 | bsr_size_show(struct device *dev, struct device_attribute *attr, char *buf) |
79 | { |
80 | struct bsr_dev *bsr_dev = dev_get_drvdata(dev); |
81 | return sprintf(buf, fmt: "%u\n" , bsr_dev->bsr_bytes); |
82 | } |
83 | static DEVICE_ATTR_RO(bsr_size); |
84 | |
85 | static ssize_t |
86 | bsr_stride_show(struct device *dev, struct device_attribute *attr, char *buf) |
87 | { |
88 | struct bsr_dev *bsr_dev = dev_get_drvdata(dev); |
89 | return sprintf(buf, fmt: "%u\n" , bsr_dev->bsr_stride); |
90 | } |
91 | static DEVICE_ATTR_RO(bsr_stride); |
92 | |
93 | static ssize_t |
94 | bsr_length_show(struct device *dev, struct device_attribute *attr, char *buf) |
95 | { |
96 | struct bsr_dev *bsr_dev = dev_get_drvdata(dev); |
97 | return sprintf(buf, fmt: "%llu\n" , bsr_dev->bsr_len); |
98 | } |
99 | static DEVICE_ATTR_RO(bsr_length); |
100 | |
101 | static struct attribute *bsr_dev_attrs[] = { |
102 | &dev_attr_bsr_size.attr, |
103 | &dev_attr_bsr_stride.attr, |
104 | &dev_attr_bsr_length.attr, |
105 | NULL, |
106 | }; |
107 | ATTRIBUTE_GROUPS(bsr_dev); |
108 | |
109 | static const struct class bsr_class = { |
110 | .name = "bsr" , |
111 | .dev_groups = bsr_dev_groups, |
112 | }; |
113 | |
114 | static int bsr_mmap(struct file *filp, struct vm_area_struct *vma) |
115 | { |
116 | unsigned long size = vma->vm_end - vma->vm_start; |
117 | struct bsr_dev *dev = filp->private_data; |
118 | int ret; |
119 | |
120 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
121 | |
122 | /* check for the case of a small BSR device and map one 4k page for it*/ |
123 | if (dev->bsr_len < PAGE_SIZE && size == PAGE_SIZE) |
124 | ret = remap_4k_pfn(vma, vma->vm_start, dev->bsr_addr >> 12, |
125 | vma->vm_page_prot); |
126 | else if (size <= dev->bsr_len) |
127 | ret = io_remap_pfn_range(vma, addr: vma->vm_start, |
128 | pfn: dev->bsr_addr >> PAGE_SHIFT, |
129 | size, prot: vma->vm_page_prot); |
130 | else |
131 | return -EINVAL; |
132 | |
133 | if (ret) |
134 | return -EAGAIN; |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | static int bsr_open(struct inode *inode, struct file *filp) |
140 | { |
141 | struct cdev *cdev = inode->i_cdev; |
142 | struct bsr_dev *dev = container_of(cdev, struct bsr_dev, bsr_cdev); |
143 | |
144 | filp->private_data = dev; |
145 | return 0; |
146 | } |
147 | |
148 | static const struct file_operations bsr_fops = { |
149 | .owner = THIS_MODULE, |
150 | .mmap = bsr_mmap, |
151 | .open = bsr_open, |
152 | .llseek = noop_llseek, |
153 | }; |
154 | |
155 | static void bsr_cleanup_devs(void) |
156 | { |
157 | struct bsr_dev *cur, *n; |
158 | |
159 | list_for_each_entry_safe(cur, n, &bsr_devs, bsr_list) { |
160 | if (cur->bsr_device) { |
161 | cdev_del(&cur->bsr_cdev); |
162 | device_del(dev: cur->bsr_device); |
163 | } |
164 | list_del(entry: &cur->bsr_list); |
165 | kfree(objp: cur); |
166 | } |
167 | } |
168 | |
169 | static int bsr_add_node(struct device_node *bn) |
170 | { |
171 | int bsr_stride_len, bsr_bytes_len, num_bsr_devs; |
172 | const u32 *bsr_stride; |
173 | const u32 *bsr_bytes; |
174 | unsigned i; |
175 | int ret = -ENODEV; |
176 | |
177 | bsr_stride = of_get_property(node: bn, name: "ibm,lock-stride" , lenp: &bsr_stride_len); |
178 | bsr_bytes = of_get_property(node: bn, name: "ibm,#lock-bytes" , lenp: &bsr_bytes_len); |
179 | |
180 | if (!bsr_stride || !bsr_bytes || |
181 | (bsr_stride_len != bsr_bytes_len)) { |
182 | printk(KERN_ERR "bsr of-node has missing/incorrect property\n" ); |
183 | return ret; |
184 | } |
185 | |
186 | num_bsr_devs = bsr_bytes_len / sizeof(u32); |
187 | |
188 | for (i = 0 ; i < num_bsr_devs; i++) { |
189 | struct bsr_dev *cur = kzalloc(size: sizeof(struct bsr_dev), |
190 | GFP_KERNEL); |
191 | struct resource res; |
192 | int result; |
193 | |
194 | if (!cur) { |
195 | printk(KERN_ERR "Unable to alloc bsr dev\n" ); |
196 | ret = -ENOMEM; |
197 | goto out_err; |
198 | } |
199 | |
200 | result = of_address_to_resource(dev: bn, index: i, r: &res); |
201 | if (result < 0) { |
202 | printk(KERN_ERR "bsr of-node has invalid reg property, skipping\n" ); |
203 | kfree(objp: cur); |
204 | continue; |
205 | } |
206 | |
207 | cur->bsr_minor = i + total_bsr_devs; |
208 | cur->bsr_addr = res.start; |
209 | cur->bsr_len = resource_size(res: &res); |
210 | cur->bsr_bytes = bsr_bytes[i]; |
211 | cur->bsr_stride = bsr_stride[i]; |
212 | cur->bsr_dev = MKDEV(bsr_major, i + total_bsr_devs); |
213 | |
214 | /* if we have a bsr_len of > 4k and less then PAGE_SIZE (64k pages) */ |
215 | /* we can only map 4k of it, so only advertise the 4k in sysfs */ |
216 | if (cur->bsr_len > 4096 && cur->bsr_len < PAGE_SIZE) |
217 | cur->bsr_len = 4096; |
218 | |
219 | switch(cur->bsr_bytes) { |
220 | case 8: |
221 | cur->bsr_type = BSR_8; |
222 | break; |
223 | case 16: |
224 | cur->bsr_type = BSR_16; |
225 | break; |
226 | case 64: |
227 | cur->bsr_type = BSR_64; |
228 | break; |
229 | case 128: |
230 | cur->bsr_type = BSR_128; |
231 | break; |
232 | case 4096: |
233 | cur->bsr_type = BSR_4096; |
234 | break; |
235 | default: |
236 | cur->bsr_type = BSR_UNKNOWN; |
237 | } |
238 | |
239 | cur->bsr_num = bsr_types[cur->bsr_type]; |
240 | snprintf(buf: cur->bsr_name, size: 32, fmt: "bsr%d_%d" , |
241 | cur->bsr_bytes, cur->bsr_num); |
242 | |
243 | cdev_init(&cur->bsr_cdev, &bsr_fops); |
244 | result = cdev_add(&cur->bsr_cdev, cur->bsr_dev, 1); |
245 | if (result) { |
246 | kfree(objp: cur); |
247 | goto out_err; |
248 | } |
249 | |
250 | cur->bsr_device = device_create(cls: &bsr_class, NULL, devt: cur->bsr_dev, |
251 | drvdata: cur, fmt: "%s" , cur->bsr_name); |
252 | if (IS_ERR(ptr: cur->bsr_device)) { |
253 | printk(KERN_ERR "device_create failed for %s\n" , |
254 | cur->bsr_name); |
255 | cdev_del(&cur->bsr_cdev); |
256 | kfree(objp: cur); |
257 | goto out_err; |
258 | } |
259 | |
260 | bsr_types[cur->bsr_type] = cur->bsr_num + 1; |
261 | list_add_tail(new: &cur->bsr_list, head: &bsr_devs); |
262 | } |
263 | |
264 | total_bsr_devs += num_bsr_devs; |
265 | |
266 | return 0; |
267 | |
268 | out_err: |
269 | |
270 | bsr_cleanup_devs(); |
271 | return ret; |
272 | } |
273 | |
274 | static int bsr_create_devs(struct device_node *bn) |
275 | { |
276 | int ret; |
277 | |
278 | while (bn) { |
279 | ret = bsr_add_node(bn); |
280 | if (ret) { |
281 | of_node_put(node: bn); |
282 | return ret; |
283 | } |
284 | bn = of_find_compatible_node(from: bn, NULL, compat: "ibm,bsr" ); |
285 | } |
286 | return 0; |
287 | } |
288 | |
289 | static int __init bsr_init(void) |
290 | { |
291 | struct device_node *np; |
292 | dev_t bsr_dev; |
293 | int ret = -ENODEV; |
294 | |
295 | np = of_find_compatible_node(NULL, NULL, compat: "ibm,bsr" ); |
296 | if (!np) |
297 | goto out_err; |
298 | |
299 | ret = class_register(class: &bsr_class); |
300 | if (ret) |
301 | goto out_err_1; |
302 | |
303 | ret = alloc_chrdev_region(&bsr_dev, 0, BSR_MAX_DEVS, "bsr" ); |
304 | bsr_major = MAJOR(bsr_dev); |
305 | if (ret < 0) { |
306 | printk(KERN_ERR "alloc_chrdev_region() failed for bsr\n" ); |
307 | goto out_err_2; |
308 | } |
309 | |
310 | ret = bsr_create_devs(bn: np); |
311 | if (ret < 0) { |
312 | np = NULL; |
313 | goto out_err_3; |
314 | } |
315 | |
316 | return 0; |
317 | |
318 | out_err_3: |
319 | unregister_chrdev_region(bsr_dev, BSR_MAX_DEVS); |
320 | |
321 | out_err_2: |
322 | class_unregister(class: &bsr_class); |
323 | |
324 | out_err_1: |
325 | of_node_put(node: np); |
326 | |
327 | out_err: |
328 | |
329 | return ret; |
330 | } |
331 | |
332 | static void __exit bsr_exit(void) |
333 | { |
334 | |
335 | bsr_cleanup_devs(); |
336 | |
337 | class_unregister(class: &bsr_class); |
338 | |
339 | if (bsr_major) |
340 | unregister_chrdev_region(MKDEV(bsr_major, 0), BSR_MAX_DEVS); |
341 | } |
342 | |
343 | module_init(bsr_init); |
344 | module_exit(bsr_exit); |
345 | MODULE_LICENSE("GPL" ); |
346 | MODULE_AUTHOR("Sonny Rao <sonnyrao@us.ibm.com>" ); |
347 | |