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