mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
4.4 KiB
205 lines
4.4 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* PowerNV SCOM bus debugfs interface |
|
* |
|
* Copyright 2010 Benjamin Herrenschmidt, IBM Corp |
|
* <[email protected]> |
|
* and David Gibson, IBM Corporation. |
|
* Copyright 2013 IBM Corp. |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/of.h> |
|
#include <linux/bug.h> |
|
#include <linux/gfp.h> |
|
#include <linux/slab.h> |
|
#include <linux/uaccess.h> |
|
|
|
#include <asm/machdep.h> |
|
#include <asm/firmware.h> |
|
#include <asm/opal.h> |
|
#include <asm/debugfs.h> |
|
#include <asm/prom.h> |
|
|
|
static u64 opal_scom_unmangle(u64 addr) |
|
{ |
|
u64 tmp; |
|
|
|
/* |
|
* XSCOM addresses use the top nibble to set indirect mode and |
|
* its form. Bits 4-11 are always 0. |
|
* |
|
* Because the debugfs interface uses signed offsets and shifts |
|
* the address left by 3, we basically cannot use the top 4 bits |
|
* of the 64-bit address, and thus cannot use the indirect bit. |
|
* |
|
* To deal with that, we support the indirect bits being in |
|
* bits 4-7 (IBM notation) instead of bit 0-3 in this API, we |
|
* do the conversion here. |
|
* |
|
* For in-kernel use, we don't need to do this mangling. In |
|
* kernel won't have bits 4-7 set. |
|
* |
|
* So: |
|
* debugfs will always set 0-3 = 0 and clear 4-7 |
|
* kernel will always clear 0-3 = 0 and set 4-7 |
|
*/ |
|
tmp = addr; |
|
tmp &= 0x0f00000000000000; |
|
addr &= 0xf0ffffffffffffff; |
|
addr |= tmp << 4; |
|
|
|
return addr; |
|
} |
|
|
|
static int opal_scom_read(uint32_t chip, uint64_t addr, u64 reg, u64 *value) |
|
{ |
|
int64_t rc; |
|
__be64 v; |
|
|
|
reg = opal_scom_unmangle(addr + reg); |
|
rc = opal_xscom_read(chip, reg, (__be64 *)__pa(&v)); |
|
if (rc) { |
|
*value = 0xfffffffffffffffful; |
|
return -EIO; |
|
} |
|
*value = be64_to_cpu(v); |
|
return 0; |
|
} |
|
|
|
static int opal_scom_write(uint32_t chip, uint64_t addr, u64 reg, u64 value) |
|
{ |
|
int64_t rc; |
|
|
|
reg = opal_scom_unmangle(addr + reg); |
|
rc = opal_xscom_write(chip, reg, value); |
|
if (rc) |
|
return -EIO; |
|
return 0; |
|
} |
|
|
|
struct scom_debug_entry { |
|
u32 chip; |
|
struct debugfs_blob_wrapper path; |
|
char name[16]; |
|
}; |
|
|
|
static ssize_t scom_debug_read(struct file *filp, char __user *ubuf, |
|
size_t count, loff_t *ppos) |
|
{ |
|
struct scom_debug_entry *ent = filp->private_data; |
|
u64 __user *ubuf64 = (u64 __user *)ubuf; |
|
loff_t off = *ppos; |
|
ssize_t done = 0; |
|
u64 reg, reg_base, reg_cnt, val; |
|
int rc; |
|
|
|
if (off < 0 || (off & 7) || (count & 7)) |
|
return -EINVAL; |
|
reg_base = off >> 3; |
|
reg_cnt = count >> 3; |
|
|
|
for (reg = 0; reg < reg_cnt; reg++) { |
|
rc = opal_scom_read(ent->chip, reg_base, reg, &val); |
|
if (!rc) |
|
rc = put_user(val, ubuf64); |
|
if (rc) { |
|
if (!done) |
|
done = rc; |
|
break; |
|
} |
|
ubuf64++; |
|
*ppos += 8; |
|
done += 8; |
|
} |
|
return done; |
|
} |
|
|
|
static ssize_t scom_debug_write(struct file *filp, const char __user *ubuf, |
|
size_t count, loff_t *ppos) |
|
{ |
|
struct scom_debug_entry *ent = filp->private_data; |
|
u64 __user *ubuf64 = (u64 __user *)ubuf; |
|
loff_t off = *ppos; |
|
ssize_t done = 0; |
|
u64 reg, reg_base, reg_cnt, val; |
|
int rc; |
|
|
|
if (off < 0 || (off & 7) || (count & 7)) |
|
return -EINVAL; |
|
reg_base = off >> 3; |
|
reg_cnt = count >> 3; |
|
|
|
for (reg = 0; reg < reg_cnt; reg++) { |
|
rc = get_user(val, ubuf64); |
|
if (!rc) |
|
rc = opal_scom_write(ent->chip, reg_base, reg, val); |
|
if (rc) { |
|
if (!done) |
|
done = rc; |
|
break; |
|
} |
|
ubuf64++; |
|
done += 8; |
|
} |
|
return done; |
|
} |
|
|
|
static const struct file_operations scom_debug_fops = { |
|
.read = scom_debug_read, |
|
.write = scom_debug_write, |
|
.open = simple_open, |
|
.llseek = default_llseek, |
|
}; |
|
|
|
static int scom_debug_init_one(struct dentry *root, struct device_node *dn, |
|
int chip) |
|
{ |
|
struct scom_debug_entry *ent; |
|
struct dentry *dir; |
|
|
|
ent = kzalloc(sizeof(*ent), GFP_KERNEL); |
|
if (!ent) |
|
return -ENOMEM; |
|
|
|
ent->chip = chip; |
|
snprintf(ent->name, 16, "%08x", chip); |
|
ent->path.data = (void *)kasprintf(GFP_KERNEL, "%pOF", dn); |
|
ent->path.size = strlen((char *)ent->path.data); |
|
|
|
dir = debugfs_create_dir(ent->name, root); |
|
if (!dir) { |
|
kfree(ent->path.data); |
|
kfree(ent); |
|
return -1; |
|
} |
|
|
|
debugfs_create_blob("devspec", 0400, dir, &ent->path); |
|
debugfs_create_file("access", 0600, dir, ent, &scom_debug_fops); |
|
|
|
return 0; |
|
} |
|
|
|
static int scom_debug_init(void) |
|
{ |
|
struct device_node *dn; |
|
struct dentry *root; |
|
int chip, rc; |
|
|
|
if (!firmware_has_feature(FW_FEATURE_OPAL)) |
|
return 0; |
|
|
|
root = debugfs_create_dir("scom", powerpc_debugfs_root); |
|
if (!root) |
|
return -1; |
|
|
|
rc = 0; |
|
for_each_node_with_property(dn, "scom-controller") { |
|
chip = of_get_ibm_chip_id(dn); |
|
WARN_ON(chip == -1); |
|
rc |= scom_debug_init_one(root, dn, chip); |
|
} |
|
|
|
return rc; |
|
} |
|
device_initcall(scom_debug_init);
|
|
|