/*
* Copyright (c) International Business Machines Corp., 2001
* Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* This pci and pci-express testing kernel module will allow test calls
* to be driven through various ioctl calls in a
* user space program that has attained the appropriate
* file descriptor for this device. For the functions of
* this module to work correctly there must be a pci / pci-express
* device somewhere in the system. The tests do not need
* a specific device, and the first pci device available
* will be grabbed.
*
* author: Sean Ruyle (srruyle@us.ibm.com)
* date: 5/20/2003
* PCI-Express test scripts author: Amit Khanna (amit.khanna@intel.com)
* date: 8/20/2004
*
* file: tpci.c,
* module: tpci
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include "tpci.h"
MODULE_AUTHOR("Sean Ruyle <srruyle@us.ibm.com>");
MODULE_AUTHOR("Amit Khanna <amit.khanna@intel.com>");
MODULE_AUTHOR("Copyright (c) 2013 Oracle and/or its affiliates");
MODULE_DESCRIPTION("LTP PCI Test");
MODULE_LICENSE("GPL");
#define prk_err(fmt, ...) \
pr_err(PCI_DEVICE_NAME ": " fmt "\n", ##__VA_ARGS__)
#define prk_info(fmt, ...) \
pr_info(PCI_DEVICE_NAME ": " fmt "\n", ##__VA_ARGS__)
#define prk_debug(fmt, ...) \
pr_debug(PCI_DEVICE_NAME ": " fmt "\n", ##__VA_ARGS__)
#define TPASS 0
#define TFAIL 1
#define TSKIP 32
static DEFINE_PCI_DEVICE_TABLE(ltp_pci_tbl) = {
{ PCI_DEVICE(PCI_ANY_ID, PCI_ANY_ID) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ltp_pci_tbl);
static int ltp_pci_probe(struct pci_dev *pci_dev,
const struct pci_device_id *pci_ent)
{
return 0;
}
static struct pci_driver ltp_pci_driver = {
.name = "LTP_PCI_DRIVER",
.id_table = ltp_pci_tbl,
.probe = ltp_pci_probe,
};
static int pci_registered;
struct tpci_user {
struct pci_dev *dev;
struct pci_bus *bus;
struct pci_driver *drv;
uint32_t state[16];
};
static struct tpci_user ltp_pci;
/*
* probe_pci_dev
* find a pci device that can be used for other test
* calls in this kernel module.
*/
static int probe_pci_dev(unsigned int bus, unsigned int slot)
{
struct pci_dev *dev;
if (ltp_pci.dev) {
pci_dev_put(ltp_pci.dev);
ltp_pci.dev = NULL;
}
dev = pci_get_bus_and_slot(bus, slot);
if (!dev || !dev->driver)
return -ENODEV;
prk_info("found pci_dev '%s', bus %u, devfn %u",
pci_name(dev), bus, slot);
ltp_pci.dev = dev;
ltp_pci.bus = dev->bus;
prk_info("Bus number: %d", dev->bus->number);
return 0;
}
/*
* pci_enable
* enable a pci device so that it may be used in
* later testing in the user test program
*/
static int pci_enable(void)
{
struct pci_dev *dev = ltp_pci.dev;
prk_info("enable pci device");
/* check if can enable the device pointer */
if (!dev) {
prk_err("dev is NULL");
return TFAIL;
}
if (pci_enable_device(dev)) {
prk_err("failed to enable pci device");
return TFAIL;
}
prk_info("enabled pci device");
return TPASS;
}
static int pci_disable(void)
{
struct pci_dev *dev = ltp_pci.dev;
prk_info("disable pci device");
/* check if device pointer exists */
if (!dev) {
prk_err("dev is NULL");
return TFAIL;
}
prk_info("is pci enabled '%d', is managed '%d'",
pci_is_enabled(dev), pci_is_managed(dev));
pci_release_regions(dev);
pci_disable_device(dev);
if (dev->current_state == PCI_D3hot ||
dev->current_state == PCI_D3cold) {
prk_info("disabled pci device, state '%s'",
pci_power_name(dev->current_state));
return TPASS;
}
prk_err("failed to disable pci device, state '%s'",
pci_power_name(dev->current_state));
return TFAIL;
}
/*
* find_bus
* call to pci_find_bus, use values from bus
* pointer in ltp_pci, make sure that returns
* bus with same values
*/
static int test_find_bus(void)
{
int num = ltp_pci.bus->number;
struct pci_bus *temp = NULL;
prk_info("find bus");
temp = pci_find_bus(pci_domain_nr(ltp_pci.bus), num);
if (!temp) {
prk_info("pci_find_bus failed");
return TFAIL;
} else if (temp->number != num) {
prk_err("returned bus pointer w/ wrong bus number");
return TFAIL;
}
prk_info("success returned bus pointer");
return TPASS;
}
/*
* find_class
* call to pci_find_class, using values from the
* pci_dev pointer in ltp_pci structure
*/
static int test_find_class(void)
{
unsigned int num = ltp_pci.dev->class;
struct pci_dev *temp = NULL;
prk_info("find pci class");
temp = pci_get_class(num, NULL);
if (!temp) {
prk_err("failed to find pci device from class number");
return TFAIL;
}
prk_info("found pci device from class number");
pci_dev_put(temp);
return TPASS;
}
/*
* find_device
* call to pci_find_device, using values for
* parameters from pci_dev pointer in the
* ltp_pci structure
*/
static int test_find_device(void)
{
struct pci_dev *temp = NULL;
unsigned short ven = ltp_pci.dev->vendor, dev = ltp_pci.dev->device;
prk_info("get pci device");
temp = pci_get_device(ven, dev, NULL);
if (!temp) {
prk_err("failed to find pci device from device info");
return TFAIL;
}
prk_info("found pci device from device info");
pci_dev_put(temp);
return TPASS;
}
/*
* find_subsys
* call to pci_find_subsys, use valued from
* pci_dev pointer in ltp_pci structure to
* find pci_dev from subsys info
*/
static int test_find_subsys(void)
{
struct pci_dev *temp;
unsigned short ven = ltp_pci.dev->vendor,
dev = ltp_pci.dev->device,
ss_ven = ltp_pci.dev->subsystem_vendor,
ss_dev = ltp_pci.dev->subsystem_device;
prk_info("get pci subsys");
temp = pci_get_subsys(ven, dev, ss_ven, ss_dev, NULL);
if (!temp) {
prk_err("failed to find pci device from subsys info");
return TFAIL;
}
prk_info("found pci device from subsys info");
pci_dev_put(temp);
return TPASS;
}
/*
* test_scan_bus
* call to pci_do_scan_bus, which takes
* a struct pci_bus pointer, which will
* return an integer for how far the
* function got in scanning bus
*/
static int test_scan_bus(void)
{
#ifdef CONFIG_HOTPLUG
int num;
struct pci_bus *bus = ltp_pci.bus;
prk_info("scan pci bus");
num = pci_rescan_bus(bus);
/*
* check if returned number is greater than
* max number of bus or less than 0
*/
if (num > MAX_BUS || num < 0) {
prk_err("failed scan bus");
return TFAIL;
}
prk_info("success scan bus");
return TPASS;
#else
prk_info("pci_rescan_bus() is not supported");
return TSKIP;
#endif
}
/*
* test_slot_scan
* make call to pci_scan_slot, which will
* find the device pointer and setup the
* device info
*/
static int test_slot_scan(void)
{
int ret, num = ltp_pci.dev->devfn;
struct pci_bus *bus = ltp_pci.bus;
prk_info("scan pci slot");
ret = pci_scan_slot(bus, num);
if (ret >= 0) {
prk_info("found '%d' devices from scan slot", ret);
return TPASS;
}
prk_err("pci_scan_slot failed");
return TFAIL;
}
/*
* test_bus_add_devices
* make call to pci_bus_add_devices,
* which will check the device pointer
* that is passed in for more devices
* that it can add
*/
static int test_bus_add_devices(void)
{
struct pci_bus *bus = ltp_pci.bus;
prk_info("add bus device");
pci_bus_add_devices(bus);
if (bus) {
prk_info("called bus_add_device");
return TPASS;
}
prk_err("bus_add_device failed");
return TFAIL;
}
/*
* test_enable_bridges
* make call to pci_enable_bridges,
* use bus pointer from the ltp_pci
* structure
*/
static int test_enable_bridges(void)
{
struct pci_bus *bus = ltp_pci.bus;
prk_info("enable bridges");
pci_enable_bridges(bus);
if (bus) {
prk_info("called enable bridges");
return TPASS;
}
prk_err("enable_bridges failed");
return TFAIL;
}
/*
* test_match_device
* make call to pci_match_device, returns a
* pci_device_id pointer
*/
static int test_match_device(void)
{
struct pci_dev *dev = ltp_pci.dev;
struct pci_driver *drv;
const struct pci_device_id *id;
prk_info("test pci_device_id()");
drv = pci_dev_driver(dev);
if (!drv) {
prk_err("driver pointer not allocated for pci_dev");
return TFAIL;
}
id = pci_match_id(drv->id_table, dev);
if (id) {
prk_info("match device success");
return TPASS;
}
prk_err("failed return pci_device_id");
return TFAIL;
}
/*
* test_reg_driver
* make call to pci_register_driver, which will
* register the driver for a device with the
* system
*/
static int test_reg_driver(void)
{
prk_info("test pci_register_driver");
if (pci_register_driver(<p_pci_driver)) {
prk_err("unsuccessful registering pci driver");
return TFAIL;
}
pci_registered = 1;
prk_info("success driver register");
return TPASS;
}
/*
* test_unreg_driver
* make call to pci_unregister_driver, which will
* unregister the driver for a device from the system
*/
static int test_unreg_driver(void)
{
pci_unregister_driver(<p_pci_driver);
pci_registered = 0;
return TPASS;
}
/*
* test_assign_resources
* make calls to pci_assign_resource, will need
* to setup a dev pointer and resource pointer,
*/
static int test_assign_resources(void)
{
int i, ret, rc = 0;
struct pci_dev *dev = ltp_pci.dev;
struct resource *r;
prk_info("assign resources");
for (i = 0; i < 7; ++i) {
prk_info("assign resource #%d", i);
r = &dev->resource[i];
prk_info("name = %s, flags = %lu, start 0x%lx, end 0x%lx",
r->name, r->flags,
(unsigned long)r->start, (unsigned long)r->end);
if (r->flags & IORESOURCE_MEM &&
r->flags & IORESOURCE_PREFETCH) {
ret = pci_assign_resource(dev, i);
prk_info("assign resource to '%d', ret '%d'", i, ret);
rc |= (ret < 0 && ret != -EBUSY) ? TFAIL : TPASS;
}
}
/*
* enable device after call to assign resource
* because might error if (!r->start && r->end)
*/
if (pci_enable_device(dev))
return TFAIL;
return rc;
}
/*
* test_save_state
* make call to pci_save_state, takes in a u32*
* buffer
*/
static int test_save_state(void)
{
struct pci_dev *dev = ltp_pci.dev;
prk_info("save state");
if (pci_save_state(dev)) {
prk_err("failed save state");
return TFAIL;
}
prk_info("saved state of device");
return TPASS;
}
/*
* test_restore_state
* make call to pci_restore_state, get the state buffer
* should have been previously filled out by save state
*/
static int test_restore_state(void)
{
struct pci_dev *dev = ltp_pci.dev;
prk_info("restore state");
pci_restore_state(dev);
return TPASS;
}
/*
* test_find_cap
* make call to pci_find_capability, which
* will determine if a device has a certain
* capability, use second parameter to specify
* which capability you are looking for
*/
static int test_find_cap(void)
{
struct pci_dev *dev = ltp_pci.dev;
prk_info("find device capability");
if (pci_find_capability(dev, PCI_CAP_ID_PM))
prk_info("does not have tested capability");
else
prk_info("device has PM capability");
return TPASS;
}
/*
* test_read_pci_exp_config
* make call to pci_config_read and determine if
* the PCI-Express enhanced config space of this
* device can be read successfully.
*/
static int test_read_pci_exp_config(void)
{
int pos;
u32 header;
struct pci_dev *dev = ltp_pci.dev;
/* skip the test if device doesn't have PCIe capability */
pos = pci_pcie_cap(dev);
if (!pos) {
prk_info("device doesn't have PCI-EXP capability");
return TSKIP;
}
prk_info("read the PCI Express configuration registers at 0x%x", pos);
if (pci_read_config_dword(dev, pos, &header)) {
prk_err("failed to read config dword");
return TFAIL;
}
/* comparing the value read with PCI_CAP_ID_EXP macro */
if ((header & 0x000000ff) == PCI_CAP_ID_EXP) {
prk_info("correct val read using PCIE driver installed: 0x%x",
header);
return TPASS;
}
prk_err("incorrect val read. PCIE driver/device not installed: 0x%x",
header);
return TFAIL;
}
static int test_case(unsigned int cmd)
{
int rc = TSKIP;
switch (cmd) {
case PCI_ENABLE:
rc = pci_enable();
break;
case PCI_DISABLE:
rc = pci_disable();
break;
case FIND_BUS:
rc = test_find_bus();
break;
case FIND_CLASS:
rc = test_find_class();
break;
case FIND_DEVICE:
rc = test_find_device();
break;
case FIND_SUBSYS:
rc = test_find_subsys();
break;
case BUS_SCAN:
rc = test_scan_bus();
break;
case SLOT_SCAN:
rc = test_slot_scan();
break;
case BUS_ADD_DEVICES:
rc = test_bus_add_devices();
break;
case ENABLE_BRIDGES:
rc = test_enable_bridges();
break;
case MATCH_DEVICE:
rc = test_match_device();
break;
case REG_DRIVER:
rc = test_reg_driver();
break;
case UNREG_DRIVER:
rc = test_unreg_driver();
break;
case PCI_RESOURCES:
rc = test_assign_resources();
break;
case SAVE_STATE:
rc = test_save_state();
break;
case RESTORE_STATE:
rc = test_restore_state();
break;
case FIND_CAP:
rc = test_find_cap();
break;
case PCI_EXP_CAP_CONFIG:
rc = test_read_pci_exp_config();
break;
default:
prk_info("mismatching test-case command %d", cmd);
break;
}
return rc;
}
/*
* Test-case result,
* if test is passed, value will be set to 0
*/
static int test_result;
static void device_release(struct device *dev)
{
prk_info("device released\n");
}
static struct device tdev = {
.init_name = PCI_DEVICE_NAME,
.release = device_release,
};
/* print test result to sysfs file */
static ssize_t sys_result(struct device *dev,
struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%d\n", test_result);
}
static DEVICE_ATTR(result, S_IRUSR, sys_result, NULL);
static ssize_t sys_tcase(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int tc = 0;
sscanf(buf, "%d", &tc);
prk_info("test-case %d", tc);
test_result = test_case(tc);
return count;
}
static DEVICE_ATTR(tcase, S_IWUSR, NULL, sys_tcase);
static ssize_t sys_bus_slot(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int res, bus, slot;
int ret;
sscanf(buf, "%u", &res);
bus = res >> 8 & 0xFF;
slot = res & 0xFF;
ret = probe_pci_dev(bus, slot);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR(bus_slot, S_IWUSR, NULL, sys_bus_slot);
static int tpci_init_module(void)
{
int err = 0;
prk_info("Starting module");
err = device_register(&tdev);
if (err) {
prk_err("Unable to register device");
goto err0;
}
prk_info("device registered\n");
err = device_create_file(&tdev, &dev_attr_result);
if (err) {
prk_err("Can't create sysfs file 'result'");
goto err1;
}
err = device_create_file(&tdev, &dev_attr_tcase);
if (err) {
prk_err(": Can't create sysfs file 'tc'");
goto err2;
}
err = device_create_file(&tdev, &dev_attr_bus_slot);
if (err) {
prk_err(": Can't create sysfs file 'bus_slot'");
goto err3;
}
return 0;
err3:
device_remove_file(&tdev, &dev_attr_tcase);
err2:
device_remove_file(&tdev, &dev_attr_result);
err1:
device_unregister(&tdev);
err0:
return err;
}
module_init(tpci_init_module)
static void tpci_exit_module(void)
{
prk_debug("Unloading module\n");
if (ltp_pci.dev)
pci_dev_put(ltp_pci.dev);
if (pci_registered)
pci_unregister_driver(<p_pci_driver);
device_remove_file(&tdev, &dev_attr_result);
device_remove_file(&tdev, &dev_attr_tcase);
device_remove_file(&tdev, &dev_attr_bus_slot);
device_unregister(&tdev);
}
module_exit(tpci_exit_module)