162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* IBM POWER Barrier Synchronization Register Driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright IBM Corporation 2008
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Author: Sonny Rao <sonnyrao@us.ibm.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/device.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/of.h>
1262306a36Sopenharmony_ci#include <linux/of_address.h>
1362306a36Sopenharmony_ci#include <linux/fs.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/cdev.h>
1662306a36Sopenharmony_ci#include <linux/list.h>
1762306a36Sopenharmony_ci#include <linux/mm.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <asm/io.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci This driver exposes a special register which can be used for fast
2362306a36Sopenharmony_ci synchronization across a large SMP machine.  The hardware is exposed
2462306a36Sopenharmony_ci as an array of bytes where each process will write to one of the bytes to
2562306a36Sopenharmony_ci indicate it has finished the current stage and this update is broadcast to
2662306a36Sopenharmony_ci all processors without having to bounce a cacheline between them. In
2762306a36Sopenharmony_ci POWER5 and POWER6 there is one of these registers per SMP,  but it is
2862306a36Sopenharmony_ci presented in two forms; first, it is given as a whole and then as a number
2962306a36Sopenharmony_ci of smaller registers which alias to parts of the single whole register.
3062306a36Sopenharmony_ci This can potentially allow multiple groups of processes to each have their
3162306a36Sopenharmony_ci own private synchronization device.
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci Note that this hardware *must* be written to using *only* single byte writes.
3462306a36Sopenharmony_ci It may be read using 1, 2, 4, or 8 byte loads which must be aligned since
3562306a36Sopenharmony_ci this region is treated as cache-inhibited  processes should also use a
3662306a36Sopenharmony_ci full sync before and after writing to the BSR to ensure all stores and
3762306a36Sopenharmony_ci the BSR update have made it to all chips in the system
3862306a36Sopenharmony_ci*/
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/* This is arbitrary number, up to Power6 it's been 17 or fewer  */
4162306a36Sopenharmony_ci#define BSR_MAX_DEVS (32)
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistruct bsr_dev {
4462306a36Sopenharmony_ci	u64      bsr_addr;     /* Real address */
4562306a36Sopenharmony_ci	u64      bsr_len;      /* length of mem region we can map */
4662306a36Sopenharmony_ci	unsigned bsr_bytes;    /* size of the BSR reg itself */
4762306a36Sopenharmony_ci	unsigned bsr_stride;   /* interval at which BSR repeats in the page */
4862306a36Sopenharmony_ci	unsigned bsr_type;     /* maps to enum below */
4962306a36Sopenharmony_ci	unsigned bsr_num;      /* bsr id number for its type */
5062306a36Sopenharmony_ci	int      bsr_minor;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	struct list_head bsr_list;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	dev_t    bsr_dev;
5562306a36Sopenharmony_ci	struct cdev bsr_cdev;
5662306a36Sopenharmony_ci	struct device *bsr_device;
5762306a36Sopenharmony_ci	char     bsr_name[32];
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci};
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic unsigned total_bsr_devs;
6262306a36Sopenharmony_cistatic LIST_HEAD(bsr_devs);
6362306a36Sopenharmony_cistatic int bsr_major;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cienum {
6662306a36Sopenharmony_ci	BSR_8    = 0,
6762306a36Sopenharmony_ci	BSR_16   = 1,
6862306a36Sopenharmony_ci	BSR_64   = 2,
6962306a36Sopenharmony_ci	BSR_128  = 3,
7062306a36Sopenharmony_ci	BSR_4096 = 4,
7162306a36Sopenharmony_ci	BSR_UNKNOWN = 5,
7262306a36Sopenharmony_ci	BSR_MAX  = 6,
7362306a36Sopenharmony_ci};
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic unsigned bsr_types[BSR_MAX];
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic ssize_t
7862306a36Sopenharmony_cibsr_size_show(struct device *dev, struct device_attribute *attr, char *buf)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	struct bsr_dev *bsr_dev = dev_get_drvdata(dev);
8162306a36Sopenharmony_ci	return sprintf(buf, "%u\n", bsr_dev->bsr_bytes);
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_cistatic DEVICE_ATTR_RO(bsr_size);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic ssize_t
8662306a36Sopenharmony_cibsr_stride_show(struct device *dev, struct device_attribute *attr, char *buf)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	struct bsr_dev *bsr_dev = dev_get_drvdata(dev);
8962306a36Sopenharmony_ci	return sprintf(buf, "%u\n", bsr_dev->bsr_stride);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(bsr_stride);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic ssize_t
9462306a36Sopenharmony_cibsr_length_show(struct device *dev, struct device_attribute *attr, char *buf)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct bsr_dev *bsr_dev = dev_get_drvdata(dev);
9762306a36Sopenharmony_ci	return sprintf(buf, "%llu\n", bsr_dev->bsr_len);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_cistatic DEVICE_ATTR_RO(bsr_length);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic struct attribute *bsr_dev_attrs[] = {
10262306a36Sopenharmony_ci	&dev_attr_bsr_size.attr,
10362306a36Sopenharmony_ci	&dev_attr_bsr_stride.attr,
10462306a36Sopenharmony_ci	&dev_attr_bsr_length.attr,
10562306a36Sopenharmony_ci	NULL,
10662306a36Sopenharmony_ci};
10762306a36Sopenharmony_ciATTRIBUTE_GROUPS(bsr_dev);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic const struct class bsr_class = {
11062306a36Sopenharmony_ci	.name		= "bsr",
11162306a36Sopenharmony_ci	.dev_groups	= bsr_dev_groups,
11262306a36Sopenharmony_ci};
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic int bsr_mmap(struct file *filp, struct vm_area_struct *vma)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	unsigned long size   = vma->vm_end - vma->vm_start;
11762306a36Sopenharmony_ci	struct bsr_dev *dev = filp->private_data;
11862306a36Sopenharmony_ci	int ret;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	/* check for the case of a small BSR device and map one 4k page for it*/
12362306a36Sopenharmony_ci	if (dev->bsr_len < PAGE_SIZE && size == PAGE_SIZE)
12462306a36Sopenharmony_ci		ret = remap_4k_pfn(vma, vma->vm_start, dev->bsr_addr >> 12,
12562306a36Sopenharmony_ci				   vma->vm_page_prot);
12662306a36Sopenharmony_ci	else if (size <= dev->bsr_len)
12762306a36Sopenharmony_ci		ret = io_remap_pfn_range(vma, vma->vm_start,
12862306a36Sopenharmony_ci					 dev->bsr_addr >> PAGE_SHIFT,
12962306a36Sopenharmony_ci					 size, vma->vm_page_prot);
13062306a36Sopenharmony_ci	else
13162306a36Sopenharmony_ci		return -EINVAL;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (ret)
13462306a36Sopenharmony_ci		return -EAGAIN;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return 0;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic int bsr_open(struct inode *inode, struct file *filp)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct cdev *cdev = inode->i_cdev;
14262306a36Sopenharmony_ci	struct bsr_dev *dev = container_of(cdev, struct bsr_dev, bsr_cdev);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	filp->private_data = dev;
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic const struct file_operations bsr_fops = {
14962306a36Sopenharmony_ci	.owner = THIS_MODULE,
15062306a36Sopenharmony_ci	.mmap  = bsr_mmap,
15162306a36Sopenharmony_ci	.open  = bsr_open,
15262306a36Sopenharmony_ci	.llseek = noop_llseek,
15362306a36Sopenharmony_ci};
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic void bsr_cleanup_devs(void)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	struct bsr_dev *cur, *n;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	list_for_each_entry_safe(cur, n, &bsr_devs, bsr_list) {
16062306a36Sopenharmony_ci		if (cur->bsr_device) {
16162306a36Sopenharmony_ci			cdev_del(&cur->bsr_cdev);
16262306a36Sopenharmony_ci			device_del(cur->bsr_device);
16362306a36Sopenharmony_ci		}
16462306a36Sopenharmony_ci		list_del(&cur->bsr_list);
16562306a36Sopenharmony_ci		kfree(cur);
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int bsr_add_node(struct device_node *bn)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	int bsr_stride_len, bsr_bytes_len, num_bsr_devs;
17262306a36Sopenharmony_ci	const u32 *bsr_stride;
17362306a36Sopenharmony_ci	const u32 *bsr_bytes;
17462306a36Sopenharmony_ci	unsigned i;
17562306a36Sopenharmony_ci	int ret = -ENODEV;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	bsr_stride = of_get_property(bn, "ibm,lock-stride", &bsr_stride_len);
17862306a36Sopenharmony_ci	bsr_bytes  = of_get_property(bn, "ibm,#lock-bytes", &bsr_bytes_len);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	if (!bsr_stride || !bsr_bytes ||
18162306a36Sopenharmony_ci	    (bsr_stride_len != bsr_bytes_len)) {
18262306a36Sopenharmony_ci		printk(KERN_ERR "bsr of-node has missing/incorrect property\n");
18362306a36Sopenharmony_ci		return ret;
18462306a36Sopenharmony_ci	}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	num_bsr_devs = bsr_bytes_len / sizeof(u32);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	for (i = 0 ; i < num_bsr_devs; i++) {
18962306a36Sopenharmony_ci		struct bsr_dev *cur = kzalloc(sizeof(struct bsr_dev),
19062306a36Sopenharmony_ci					      GFP_KERNEL);
19162306a36Sopenharmony_ci		struct resource res;
19262306a36Sopenharmony_ci		int result;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci		if (!cur) {
19562306a36Sopenharmony_ci			printk(KERN_ERR "Unable to alloc bsr dev\n");
19662306a36Sopenharmony_ci			ret = -ENOMEM;
19762306a36Sopenharmony_ci			goto out_err;
19862306a36Sopenharmony_ci		}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		result = of_address_to_resource(bn, i, &res);
20162306a36Sopenharmony_ci		if (result < 0) {
20262306a36Sopenharmony_ci			printk(KERN_ERR "bsr of-node has invalid reg property, skipping\n");
20362306a36Sopenharmony_ci			kfree(cur);
20462306a36Sopenharmony_ci			continue;
20562306a36Sopenharmony_ci		}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		cur->bsr_minor  = i + total_bsr_devs;
20862306a36Sopenharmony_ci		cur->bsr_addr   = res.start;
20962306a36Sopenharmony_ci		cur->bsr_len    = resource_size(&res);
21062306a36Sopenharmony_ci		cur->bsr_bytes  = bsr_bytes[i];
21162306a36Sopenharmony_ci		cur->bsr_stride = bsr_stride[i];
21262306a36Sopenharmony_ci		cur->bsr_dev    = MKDEV(bsr_major, i + total_bsr_devs);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		/* if we have a bsr_len of > 4k and less then PAGE_SIZE (64k pages) */
21562306a36Sopenharmony_ci		/* we can only map 4k of it, so only advertise the 4k in sysfs */
21662306a36Sopenharmony_ci		if (cur->bsr_len > 4096 && cur->bsr_len < PAGE_SIZE)
21762306a36Sopenharmony_ci			cur->bsr_len = 4096;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		switch(cur->bsr_bytes) {
22062306a36Sopenharmony_ci		case 8:
22162306a36Sopenharmony_ci			cur->bsr_type = BSR_8;
22262306a36Sopenharmony_ci			break;
22362306a36Sopenharmony_ci		case 16:
22462306a36Sopenharmony_ci			cur->bsr_type = BSR_16;
22562306a36Sopenharmony_ci			break;
22662306a36Sopenharmony_ci		case 64:
22762306a36Sopenharmony_ci			cur->bsr_type = BSR_64;
22862306a36Sopenharmony_ci			break;
22962306a36Sopenharmony_ci		case 128:
23062306a36Sopenharmony_ci			cur->bsr_type = BSR_128;
23162306a36Sopenharmony_ci			break;
23262306a36Sopenharmony_ci		case 4096:
23362306a36Sopenharmony_ci			cur->bsr_type = BSR_4096;
23462306a36Sopenharmony_ci			break;
23562306a36Sopenharmony_ci		default:
23662306a36Sopenharmony_ci			cur->bsr_type = BSR_UNKNOWN;
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		cur->bsr_num = bsr_types[cur->bsr_type];
24062306a36Sopenharmony_ci		snprintf(cur->bsr_name, 32, "bsr%d_%d",
24162306a36Sopenharmony_ci			 cur->bsr_bytes, cur->bsr_num);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci		cdev_init(&cur->bsr_cdev, &bsr_fops);
24462306a36Sopenharmony_ci		result = cdev_add(&cur->bsr_cdev, cur->bsr_dev, 1);
24562306a36Sopenharmony_ci		if (result) {
24662306a36Sopenharmony_ci			kfree(cur);
24762306a36Sopenharmony_ci			goto out_err;
24862306a36Sopenharmony_ci		}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci		cur->bsr_device = device_create(&bsr_class, NULL, cur->bsr_dev,
25162306a36Sopenharmony_ci						cur, "%s", cur->bsr_name);
25262306a36Sopenharmony_ci		if (IS_ERR(cur->bsr_device)) {
25362306a36Sopenharmony_ci			printk(KERN_ERR "device_create failed for %s\n",
25462306a36Sopenharmony_ci			       cur->bsr_name);
25562306a36Sopenharmony_ci			cdev_del(&cur->bsr_cdev);
25662306a36Sopenharmony_ci			kfree(cur);
25762306a36Sopenharmony_ci			goto out_err;
25862306a36Sopenharmony_ci		}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci		bsr_types[cur->bsr_type] = cur->bsr_num + 1;
26162306a36Sopenharmony_ci		list_add_tail(&cur->bsr_list, &bsr_devs);
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	total_bsr_devs += num_bsr_devs;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	return 0;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci out_err:
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	bsr_cleanup_devs();
27162306a36Sopenharmony_ci	return ret;
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic int bsr_create_devs(struct device_node *bn)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	int ret;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	while (bn) {
27962306a36Sopenharmony_ci		ret = bsr_add_node(bn);
28062306a36Sopenharmony_ci		if (ret) {
28162306a36Sopenharmony_ci			of_node_put(bn);
28262306a36Sopenharmony_ci			return ret;
28362306a36Sopenharmony_ci		}
28462306a36Sopenharmony_ci		bn = of_find_compatible_node(bn, NULL, "ibm,bsr");
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci	return 0;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic int __init bsr_init(void)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	struct device_node *np;
29262306a36Sopenharmony_ci	dev_t bsr_dev;
29362306a36Sopenharmony_ci	int ret = -ENODEV;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	np = of_find_compatible_node(NULL, NULL, "ibm,bsr");
29662306a36Sopenharmony_ci	if (!np)
29762306a36Sopenharmony_ci		goto out_err;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	ret = class_register(&bsr_class);
30062306a36Sopenharmony_ci	if (ret)
30162306a36Sopenharmony_ci		goto out_err_1;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	ret = alloc_chrdev_region(&bsr_dev, 0, BSR_MAX_DEVS, "bsr");
30462306a36Sopenharmony_ci	bsr_major = MAJOR(bsr_dev);
30562306a36Sopenharmony_ci	if (ret < 0) {
30662306a36Sopenharmony_ci		printk(KERN_ERR "alloc_chrdev_region() failed for bsr\n");
30762306a36Sopenharmony_ci		goto out_err_2;
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	ret = bsr_create_devs(np);
31162306a36Sopenharmony_ci	if (ret < 0) {
31262306a36Sopenharmony_ci		np = NULL;
31362306a36Sopenharmony_ci		goto out_err_3;
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	return 0;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci out_err_3:
31962306a36Sopenharmony_ci	unregister_chrdev_region(bsr_dev, BSR_MAX_DEVS);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci out_err_2:
32262306a36Sopenharmony_ci	class_unregister(&bsr_class);
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci out_err_1:
32562306a36Sopenharmony_ci	of_node_put(np);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci out_err:
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	return ret;
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cistatic void __exit  bsr_exit(void)
33362306a36Sopenharmony_ci{
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	bsr_cleanup_devs();
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	class_unregister(&bsr_class);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	if (bsr_major)
34062306a36Sopenharmony_ci		unregister_chrdev_region(MKDEV(bsr_major, 0), BSR_MAX_DEVS);
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cimodule_init(bsr_init);
34462306a36Sopenharmony_cimodule_exit(bsr_exit);
34562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
34662306a36Sopenharmony_ciMODULE_AUTHOR("Sonny Rao <sonnyrao@us.ibm.com>");
347