215 lines
4.4 KiB
C
215 lines
4.4 KiB
C
/* Copyright 2014-2016 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* imitations under the License.
|
|
*/
|
|
|
|
#define _LARGEFILE64_SOURCE
|
|
#include <sys/mman.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <dirent.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
|
|
#include "xscom.h"
|
|
|
|
#define XSCOM_BASE_PATH "/sys/kernel/debug/powerpc/scom"
|
|
|
|
struct xscom_chip {
|
|
struct xscom_chip *next;
|
|
uint32_t chip_id;
|
|
int fd;
|
|
};
|
|
static struct xscom_chip *xscom_chips;
|
|
|
|
void xscom_for_each_chip(void (*cb)(uint32_t chip_id))
|
|
{
|
|
struct xscom_chip *c;
|
|
|
|
for (c = xscom_chips; c; c = c->next)
|
|
cb(c->chip_id);
|
|
}
|
|
|
|
static uint32_t xscom_add_chip(const char *base_path, const char *dname)
|
|
{
|
|
char nbuf[strlen(base_path) + strlen(dname) + 16];
|
|
struct xscom_chip *chip;
|
|
int fd;
|
|
|
|
snprintf(nbuf, sizeof(nbuf), "%s/%s/access", base_path, dname);
|
|
fd = open(nbuf, O_RDWR);
|
|
if (fd < 0) {
|
|
perror("Failed to open SCOM access file");
|
|
exit(1);
|
|
}
|
|
|
|
chip = malloc(sizeof(*chip));
|
|
assert(chip);
|
|
memset(chip, 0, sizeof(*chip));
|
|
chip->fd = fd;
|
|
chip->chip_id = strtoul(dname, NULL, 16);
|
|
chip->next = xscom_chips;
|
|
xscom_chips = chip;
|
|
|
|
return chip->chip_id;
|
|
}
|
|
|
|
static bool xscom_check_dirname(const char *n)
|
|
{
|
|
while(*n) {
|
|
char c = toupper(*(n++));
|
|
|
|
if ((c < 'A' || c > 'Z') &&
|
|
(c < '0' || c > '9'))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static uint32_t xscom_scan_chips(const char *base_path)
|
|
{
|
|
int i, nfiles;
|
|
struct dirent **filelist;
|
|
uint32_t lower = 0xffffffff;
|
|
|
|
nfiles = scandir(base_path, &filelist, NULL, alphasort);
|
|
if (nfiles < 0) {
|
|
perror("Error accessing sysfs scom directory");
|
|
exit(1);
|
|
}
|
|
if (nfiles == 0) {
|
|
fprintf(stderr, "No SCOM dir found in sysfs\n");
|
|
exit(1);
|
|
}
|
|
|
|
for (i = 0; i < nfiles; i++) {
|
|
struct dirent *d = filelist[i];
|
|
uint32_t id;
|
|
|
|
if (d->d_type != DT_DIR)
|
|
continue;
|
|
if (!xscom_check_dirname(d->d_name))
|
|
continue;
|
|
id = xscom_add_chip(base_path, d->d_name);
|
|
if (id < lower)
|
|
lower = id;
|
|
free(d);
|
|
}
|
|
|
|
free(filelist);
|
|
return lower;
|
|
}
|
|
|
|
static struct xscom_chip *xscom_find_chip(uint32_t chip_id)
|
|
{
|
|
struct xscom_chip *c;
|
|
|
|
for (c = xscom_chips; c; c = c->next)
|
|
if (c->chip_id == chip_id)
|
|
return c;
|
|
return NULL;
|
|
}
|
|
|
|
static uint64_t xscom_mangle_addr(uint64_t addr)
|
|
{
|
|
uint64_t tmp;
|
|
|
|
/*
|
|
* Shift the top 4 bits (indirect mode) down by 4 bits so we
|
|
* don't lose going through the debugfs interfaces.
|
|
*/
|
|
tmp = (addr & 0xf000000000000000) >> 4;
|
|
addr &= 0x00ffffffffffffff;
|
|
addr |= tmp;
|
|
|
|
/* Shift up by 3 for debugfs */
|
|
return addr << 3;
|
|
}
|
|
|
|
int xscom_read(uint32_t chip_id, uint64_t addr, uint64_t *val)
|
|
{
|
|
struct xscom_chip *c = xscom_find_chip(chip_id);
|
|
int rc;
|
|
|
|
if (!c)
|
|
return -ENODEV;
|
|
addr = xscom_mangle_addr(addr);
|
|
lseek64(c->fd, addr, SEEK_SET);
|
|
rc = read(c->fd, val, 8);
|
|
if (rc < 0)
|
|
return -errno;
|
|
if (rc != 8)
|
|
return -EIO;
|
|
return 0;
|
|
}
|
|
|
|
int xscom_write(uint32_t chip_id, uint64_t addr, uint64_t val)
|
|
{
|
|
struct xscom_chip *c = xscom_find_chip(chip_id);
|
|
int rc;
|
|
|
|
if (!c)
|
|
return -ENODEV;
|
|
addr = xscom_mangle_addr(addr);
|
|
lseek64(c->fd, addr, SEEK_SET);
|
|
rc = write(c->fd, &val, 8);
|
|
if (rc < 0)
|
|
return -errno;
|
|
if (rc != 8)
|
|
return -EIO;
|
|
return 0;
|
|
}
|
|
|
|
int xscom_read_ex(uint32_t ex_target_id, uint64_t addr, uint64_t *val)
|
|
{
|
|
uint32_t chip_id = ex_target_id >> 4;;
|
|
|
|
addr |= (ex_target_id & 0xf) << 24;
|
|
|
|
/* XXX TODO: Special wakeup ? */
|
|
|
|
return xscom_read(chip_id, addr, val);
|
|
}
|
|
|
|
int xscom_write_ex(uint32_t ex_target_id, uint64_t addr, uint64_t val)
|
|
{
|
|
uint32_t chip_id = ex_target_id >> 4;;
|
|
|
|
addr |= (ex_target_id & 0xf) << 24;
|
|
|
|
/* XXX TODO: Special wakeup ? */
|
|
|
|
return xscom_write(chip_id, addr, val);
|
|
}
|
|
|
|
bool xscom_readable(uint64_t addr)
|
|
{
|
|
/* Top nibble 9 indicates form 1 indirect, which is write only */
|
|
if (((addr >> 60) & 0xf) == 9)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
uint32_t xscom_init(void)
|
|
{
|
|
return xscom_scan_chips(XSCOM_BASE_PATH);
|
|
}
|