blob: e805af861f3260a03107f9a8ab883ccf2180c926 [file] [log] [blame]
/*
* Copyright 2012, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* pci.c: Quick PCI access, requires linux /proc/bus/pci or
* directory that has same structure
*
* FIXME: does not handle extended configuration space (>256B)
*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <inttypes.h>
#include <dirent.h>
#include "mosys/platform.h"
#include "mosys/globals.h"
#include "mosys/log.h"
#include "lib/string.h"
#include "intf/pci.h"
/* default PCI root directory */
#define PCI_PROC_DIR "/proc/bus/pci"
/* used throughout this file */
static char *pci_proc_dir;
static int pci_setup(struct platform_intf *intf)
{
pci_proc_dir = format_string("%s/%s", mosys_get_root_prefix(),
PCI_PROC_DIR);
return 0;
}
static void pci_destroy(struct platform_intf *intf)
{
free(pci_proc_dir);
}
/*
* pci_open_file - Open PCI dev file based on bus/dev/func under given root
*
* @bus: pci bus number
* @dev: pci device id
* @func: pci function id
* @rw: read=0, write=1
*
* returns valid file descriptor on success
* returns <0 on failure
*/
static int pci_open_file(int bus, int dev, int func, int rw)
{
char pcif[1024];
snprintf(pcif, sizeof(pcif), "%s/%02x/%02x.%x",
pci_proc_dir, bus, dev, func);
return open(pcif, rw);
}
/*
* pci_read_file - Read from pci configuration space
*
* @intf: platform interface
* @bus: pci bus
* @dev: pci device
* @func: pci function
* @reg: configuration register
* @length: number of bytes to read
* @data: buffer to read data into
*
* returns number of bytes read
* returns <0 to indicate failure
*/
static int pci_read_file(struct platform_intf *intf, int bus,
int dev, int func, int reg, int length, void *data)
{
int fd, rlen;
lprintf(LOG_DEBUG,
"pci_read_file: reading %02x:%02x.%x at %02x (%d bytes)\n", bus,
dev, func, reg, length);
if (length < 1 || length > 255)
return -1;
fd = pci_open_file(bus, dev, func, O_RDONLY);
if (fd < 0) {
lprintf(LOG_DEBUG,
"Unable to open PCI %02x:%02x.%1x for reading\n", bus,
dev, func);
return -1;
}
/* seek to starting register */
if (lseek(fd, reg, SEEK_SET) == (off_t) - 1) {
lperror(LOG_DEBUG,
"Unable to read from PCI %02x:%02x.%1x at 0x%x", bus,
dev, func, reg);
close(fd);
return -1;
}
/* read the data */
rlen = read(fd, data, length);
if (rlen != length)
lperror(LOG_DEBUG, "Unable to read %d bytes from PCI"
" %02x:%02x.%1x at 0x%x", length, bus, dev, func, reg);
close(fd);
return rlen;
}
/*
* pci_write_file - Write to pci configuration space
*
* @intf: platform interface
* @bus: pci bus
* @dev: pci device
* @func: pci function
* @reg: configuration register
* @size: number of bytes to read
* @data: buffer to write from
*
* returns number of bytes written
* returns <0 to indicate failure
*/
static int pci_write_file(struct platform_intf *intf, int bus, int dev,
int func, int reg, int length, const void *data)
{
int fd, wlen;
lprintf(LOG_DEBUG,
"pci_write_file: writing %02x:%02x.%x at %02x (%d bytes)\n",
bus, dev, func, reg, length);
if (length < 1 || length > 256)
return -1;
fd = pci_open_file(bus, dev, func, O_WRONLY);
if (fd < 0) {
lprintf(LOG_DEBUG,
"Unable to open PCI %02x:%02x.%1x for writing\n", bus,
dev, func);
return -1;
}
/* seek to starting register */
if (lseek(fd, reg, SEEK_SET) == (off_t) - 1) {
lperror(LOG_DEBUG, "Unable to write to PCI %02x:%02x.%1x at 0x%x",
bus, dev, func, reg);
close(fd);
return -1;
}
/* write the data */
wlen = write(fd, data, length);
if (wlen != length)
lperror(LOG_DEBUG, "Unable to write %d bytes to PCI"
" %02x:%02x.%1x at 0x%x", length, bus, dev, func, reg);
close(fd);
return wlen;
}
/*
* pci_do_foreach_in_bus - Execute function on all PCI devices on a given bus
*
* @intf: platform interface
* @bus: target pci bus
* @cb: pci callback
* @data: data supplied to callback
*
* Supplied PCI callback will cause bus enumeration to stop if
* it returns non-zero value.
*
* returns 0 to indicate success, failure otherwise.
*/
static int pci_do_foreach_in_bus(struct platform_intf *intf, int bus,
pci_callback_t cb, void *data)
{
struct dirent *d;
char path[1024];
DIR *dp;
int dev = 0, func = 0;
int ret = 0;
if (cb == NULL)
return -1;
/* directory is a bus number */
snprintf(path, sizeof(path), "%s/%02x/",
pci_proc_dir, bus);
if (!(dp = opendir(path))) {
lprintf(LOG_DEBUG, "Failed to open %s\n", path);
return -1;
}
while ((d = readdir(dp))) {
if (d->d_type != DT_REG)
continue;
/* file is a pci device.function */
if (sscanf(d->d_name, "%02x.%01x", &dev, &func) != 2) {
lprintf(LOG_DEBUG, "Invalid PCI file name: %s/%s\n",
path, d->d_name);
continue;
}
/* now exec callback, non-zero return means stop */
lprintf(LOG_DEBUG,
"Executing callback for device %02x:%02x.%01x\n",
bus, dev, func);
ret = cb(intf, bus, dev, func, data);
if (ret != 0) {
break;
}
}
closedir(dp);
return ret;
}
/*
* pci_do_foreach - Execute function on all PCI devices
*
* @intf: platform interface
* @cb: pci callback
* @data: data supplied to callback
*
* Supplied PCI callback will cause bus enumeration to stop if
* it returns non-zero value.
*
* returns 0 to indicate success, failure otherwise.
*/
static int pci_do_foreach(struct platform_intf *intf,
pci_callback_t cb, void *data)
{
struct dirent *topd;
DIR *topdp;
int ret = 0;
if (!(topdp = opendir(pci_proc_dir))) {
lprintf(LOG_DEBUG, "Failed to open %s\n", pci_proc_dir);
return -1;
}
while ((topd = readdir(topdp))) {
if (topd->d_type != DT_DIR)
continue;
/* Ignore driectories with '.' in the first part of the name. */
if (topd->d_name[0] == '.')
continue;
ret = pci_foreach_in_bus(intf,
strtol(topd->d_name, NULL, 16), cb, data);
if (ret != 0) {
closedir(topdp);
return ret;
}
}
if (topdp)
closedir(topdp);
return ret;
}
/* PCI operations based on /proc directory */
struct pci_intf pci_file_intf = {
.setup = pci_setup,
.destroy = pci_destroy,
.read = pci_read_file,
.write = pci_write_file,
.foreach = pci_do_foreach,
.foreach_in_bus = pci_do_foreach_in_bus,
};