Files
linux-nvgpu/drivers/gpu/nvgpu/os/linux/pci_power.c
Thomas Fleury 95bb19827e gpu: nvgpu: add sw quiesce
For safety build, nvgpu driver should enter SW quiesce state
in case an uncorrectable error has occurred. In this state, any
activity on the GPU should be prevented, without powering off the GPU.
Also, a minimal set of operations should be used to enter SW quiesce
state.

Entering SW quiesce state does the following:
- set sw_quiesce_pending: when this flag is set, interrupt
  handlers exit after masking interrupts. This should help mitigate
  an interrupt storm.
- wake up thread to complete quiescing.

The thread performs the following:
- set NVGPU_DRIVER_IS_DYING to prevent allocation of new resources
- disable interrupts
- disable fifo scheduling
- preempt all runlists
- set error notifier for all active channels

Note: for channels with usermode submit enabled, userspace can
still ring doorbell, but this will not trigger any work on
engines since fifo scheduling is disabled.

Jira NVGPU-3493

Change-Id: I639a32da754d8833f54dcec1fa23135721d8d89a
Signed-off-by: Thomas Fleury <tfleury@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/2172391
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
2019-08-27 10:37:21 -07:00

708 lines
15 KiB
C

/*
* Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
*/
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/ktime.h>
#include <linux/debugfs.h>
#include <nvgpu/lock.h>
#include <nvgpu/nvgpu_init.h>
#include "module.h"
#include "platform_gk20a.h"
#include "pci_power.h"
#define PCI_DEV_NAME_MAX 64
struct pci_power_stats {
u64 power_ons;
s64 power_on_lat_ns_min;
s64 power_on_lat_ns_max;
s64 power_on_lat_ns_avg;
u64 power_offs;
s64 power_off_lat_ns_min;
s64 power_off_lat_ns_max;
s64 power_off_lat_ns_avg;
};
static struct dentry *pci_power_stats_dbgfs_dentry;
struct nvgpu_pci_power {
struct list_head list;
struct nvgpu_mutex mutex;
struct nvgpu_pci_gpios gpios;
struct pci_dev *pci_dev;
char pci_dev_name[PCI_DEV_NAME_MAX];
void *pci_cookie;
struct pci_power_stats stats;
bool can_pci_gc_off;
};
static struct list_head nvgpu_pci_power_devs =
LIST_HEAD_INIT(nvgpu_pci_power_devs);
static struct nvgpu_pci_power *nvgpu_pci_get_pci_power(const char *dev_name)
{
struct nvgpu_pci_power *pp, *tmp_pp;
list_for_each_entry_safe(pp, tmp_pp, &nvgpu_pci_power_devs, list) {
if (!strcmp(dev_name, pp->pci_dev_name))
return pp;
}
return NULL;
}
int nvgpu_pci_add_pci_power(struct pci_dev *pdev)
{
struct gk20a_platform *platform;
struct nvgpu_pci_power *pp;
if (!pdev)
return -EINVAL;
pp = nvgpu_pci_get_pci_power(dev_name(&pdev->dev));
if (pp) {
pp->pci_dev = pdev;
return 0;
}
pp = kzalloc(sizeof(*pp), GFP_KERNEL);
if (!pp)
return -ENOMEM;
nvgpu_mutex_init(&pp->mutex);
pp->pci_dev = pdev;
strlcpy(pp->pci_dev_name,
dev_name(&pdev->dev), PCI_DEV_NAME_MAX);
platform = pci_get_drvdata(pdev);
pp->can_pci_gc_off = platform->can_pci_gc_off;
list_add(&pp->list, &nvgpu_pci_power_devs);
return 0;
}
static void nvgpu_free_pci_gpios(struct nvgpu_pci_gpios *pgpios);
static int nvgpu_pci_remove_pci_power(struct nvgpu_pci_power *pp)
{
list_del(&pp->list);
nvgpu_free_pci_gpios(&pp->gpios);
kfree(pp);
return 0;
}
static ssize_t probed_gpus_show(struct device_driver *drv, char *buf)
{
struct nvgpu_pci_power *pp, *tmp_pp;
ssize_t count = 0;
list_for_each_entry_safe(pp, tmp_pp, &nvgpu_pci_power_devs, list) {
count += snprintf(buf, PAGE_SIZE - count, "pci-%s\t%s\n",
pp->pci_dev_name,
pp->pci_dev ? "PoweredOn" : "PoweredOff");
}
return count;
}
static DRIVER_ATTR_RO(probed_gpus);
int nvgpu_pci_clear_pci_power(const char *dev_name)
{
struct nvgpu_pci_power *pp, *tmp_pp;
list_for_each_entry_safe(pp, tmp_pp, &nvgpu_pci_power_devs, list) {
if (!strcmp(dev_name, pp->pci_dev_name)) {
pp->pci_dev = NULL;
return 0;
}
}
return -ENODEV;
}
static int nvgpu_pci_parse_dev_name(const char *buf, size_t count, char *name)
{
int domain, bus, device, func;
int ret;
/* DDDD:BB:DD.F for domain:bus:device.function */
ret = sscanf(buf, "%x:%x:%x.%x", &domain, &bus, &device, &func);
if (ret < 4)
return -EINVAL;
(void) snprintf(name, PCI_DEV_NAME_MAX, "%04x:%02x:%02x.%1x",
domain, bus, device, func);
return 0;
}
static char *nvgpu_pci_gpio_name(int g)
{
switch (g) {
case PCI_GPIO_VBAT_PWR_ON:
return "PCI_GPIO_VBAT_PWR_ON";
case PCI_GPIO_PRSNT2:
return "PCI_GPIO_PRSNT2*";
case PCI_GPIO_PRSNT1:
return "PCI_GPIO_PRSNT1*";
case PCI_GPIO_PWR_ON:
return "PCI_GPIO_PWR_ON";
case PCI_GPIO_PG:
return "PCI_GPIO_PG";
}
return "INVALID_PCI_GPIO";
}
static void nvgpu_dump_pci_gpios(struct nvgpu_pci_gpios *pgpios, const char *f)
{
int is_in, val, i;
struct gpio_desc *gd;
pr_debug("nvgpu gpio status in %s:\n", f);
for (i = 0; i < PCI_GPIO_MAX; i++) {
if (pgpios->gpios[i] == 0) {
pr_debug("%d. %-25s: gpio not requested\n",
i, nvgpu_pci_gpio_name(i));
continue;
}
gd = gpio_to_desc(pgpios->gpios[i]);
if (gd) {
is_in = gpiod_get_direction(gd);
val = gpiod_get_value_cansleep(gd);
pr_debug("%d. %-25s gpio-%-3d dir=%s val=%s\n",
i, nvgpu_pci_gpio_name(i), pgpios->gpios[i],
is_in ? "in " : "out",
val >= 0 ? (val != 0 ? "hi" : "lo") : "? ");
} else {
pr_debug("%d. %-25s invalid gpio desc\n",
i, nvgpu_pci_gpio_name(i));
}
}
}
static void nvgpu_free_pci_gpios(struct nvgpu_pci_gpios *pgpios)
{
int i;
for (i = 0; i < PCI_GPIO_MAX; i++) {
if (pgpios->gpios[i]) {
gpio_free(pgpios->gpios[i]);
pgpios->gpios[i] = 0;
}
}
}
static int nvgpu_request_pci_gpios(struct nvgpu_pci_gpios *pgpios)
{
struct device_node *np;
int i, ret, gpio;
if (pgpios->gpios[0])
return 0;
np = of_find_node_by_name(NULL, "nvgpu");
if (!np) {
ret = -ENOENT;
goto err;
}
for (i = 0; i < PCI_GPIO_MAX; i++) {
gpio = of_get_named_gpio(np, "nvgpu-pci-gpios", i);
if (gpio < 0) {
ret = gpio;
goto err;
}
ret = gpio_request(gpio, "pci-gpio");
if (ret)
goto err;
pgpios->gpios[i] = gpio;
}
nvgpu_dump_pci_gpios(pgpios, __func__);
of_node_put(np);
return 0;
err:
of_node_put(np);
nvgpu_free_pci_gpios(pgpios);
return ret;
}
static int nvgpu_disable_pci_rail(struct nvgpu_pci_gpios *pgpios)
{
int pci_vbat_pwr_on_gpio = pgpios->gpios[PCI_GPIO_VBAT_PWR_ON];
gpio_set_value(pci_vbat_pwr_on_gpio, 0);
mdelay(PCI_VBAR_PWR_OFF_DELAY_MS);
return 0;
}
static int nvgpu_check_pci_power_good(struct nvgpu_pci_gpios *pgpios)
{
int pci_pg = pgpios->gpios[PCI_GPIO_PG];
return gpio_get_value(pci_pg) != 1 ? -EINVAL : 0;
}
static int nvgpu_enable_pci_rail(struct nvgpu_pci_gpios *pgpios)
{
int pci_vbat_pwr_on_gpio = pgpios->gpios[PCI_GPIO_VBAT_PWR_ON];
gpio_set_value(pci_vbat_pwr_on_gpio, 1);
mdelay(PCI_VBAR_PWR_ON_DELAY_MS);
return 0;
}
static int nvgpu_deassert_pci_pwr_on(struct nvgpu_pci_gpios *pgpios)
{
int pci_pwr_on = pgpios->gpios[PCI_GPIO_PWR_ON];
gpio_set_value(pci_pwr_on, 0);
mdelay(PCI_PWR_OFF_DELAY_MS);
return 0;
}
static int nvgpu_assert_pci_pwr_on(struct nvgpu_pci_gpios *pgpios)
{
int pci_pwr_on = pgpios->gpios[PCI_GPIO_PWR_ON];
gpio_set_value(pci_pwr_on, 1);
mdelay(PCI_PWR_ON_DELAY_MS);
return 0;
}
static void nvgpu_update_power_on_stats(struct nvgpu_pci_power *pp, s64 time_ns)
{
struct pci_power_stats *stats = &pp->stats;
stats->power_ons++;
if (unlikely(stats->power_on_lat_ns_min == 0)) {
stats->power_on_lat_ns_min = time_ns;
stats->power_on_lat_ns_max = time_ns;
stats->power_on_lat_ns_avg = time_ns;
return;
}
if (time_ns < stats->power_on_lat_ns_min)
stats->power_on_lat_ns_min = time_ns;
if (time_ns > stats->power_on_lat_ns_max)
stats->power_on_lat_ns_max = time_ns;
stats->power_on_lat_ns_avg =
(stats->power_on_lat_ns_avg + time_ns) >> 1;
}
static void nvgpu_update_power_off_stats(struct nvgpu_pci_power *pp,
s64 time_ns)
{
struct pci_power_stats *stats = &pp->stats;
stats->power_offs++;
if (unlikely(stats->power_off_lat_ns_min == 0)) {
stats->power_off_lat_ns_min = time_ns;
stats->power_off_lat_ns_max = time_ns;
stats->power_off_lat_ns_avg = time_ns;
return;
}
if (time_ns < stats->power_off_lat_ns_min)
stats->power_off_lat_ns_min = time_ns;
if (time_ns > stats->power_off_lat_ns_max)
stats->power_off_lat_ns_max = time_ns;
stats->power_off_lat_ns_avg =
(stats->power_off_lat_ns_avg + time_ns) >> 1;
}
#define DBG_POWER_STAT_SEQ_PRINTF(s, stat) \
seq_printf(s, "%20s:%15lld\n", #stat, pp->stats.stat)
static int debugfs_pci_power_stats_show(struct seq_file *s, void *unused)
{
struct nvgpu_pci_power *pp, *tmp_pp;
list_for_each_entry_safe(pp, tmp_pp, &nvgpu_pci_power_devs, list) {
seq_printf(s, "PCI GPU (%s) Power Stats:\n", pp->pci_dev_name);
DBG_POWER_STAT_SEQ_PRINTF(s, power_ons);
DBG_POWER_STAT_SEQ_PRINTF(s, power_on_lat_ns_min);
DBG_POWER_STAT_SEQ_PRINTF(s, power_on_lat_ns_max);
DBG_POWER_STAT_SEQ_PRINTF(s, power_on_lat_ns_avg);
DBG_POWER_STAT_SEQ_PRINTF(s, power_offs);
DBG_POWER_STAT_SEQ_PRINTF(s, power_off_lat_ns_min);
DBG_POWER_STAT_SEQ_PRINTF(s, power_off_lat_ns_max);
DBG_POWER_STAT_SEQ_PRINTF(s, power_off_lat_ns_avg);
}
return 0;
}
static int debugfs_pci_power_stats_open(struct inode *i, struct file *f)
{
return single_open(f, debugfs_pci_power_stats_show, &i->i_private);
}
static const struct file_operations debug_power_stats_fops = {
.open = debugfs_pci_power_stats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#if (!IS_ENABLED(CONFIG_PCIE_TEGRA_DW) && !IS_ENABLED(CONFIG_PCIE_TEGRA)) || \
!IS_ENABLED(CONFIG_ARCH_TEGRA_19x_SOC) || \
LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
void *tegra_pcie_detach_controller(struct pci_dev *pdev)
{
pr_err("nvgpu: detach pci controller not available\n");
return NULL;
}
int tegra_pcie_attach_controller(void *cookie)
{
pr_err("nvgpu: attach pci controller not available\n");
return -EINVAL;
}
#endif
static int nvgpu_detach_pci_gpu(struct nvgpu_pci_power *pp)
{
struct pci_dev *pdev = pp->pci_dev;
void *pci_cookie;
int ret = 0;
pci_cookie = tegra_pcie_detach_controller(pdev);
if (IS_ERR(pci_cookie)) {
ret = PTR_ERR(pci_cookie);
pr_err("nvgpu: detaching PCIe controller failed (%d)\n", ret);
return ret;
}
pp->pci_cookie = pci_cookie;
return 0;
}
static int nvgpu_attach_pci_gpu(struct nvgpu_pci_power *pp)
{
void *pci_cookie = pp->pci_cookie;
int ret = 0;
if (pci_cookie == NULL) {
pr_err("nvgpu: Invalid pci cookie\n");
return -EINVAL;
}
ret = tegra_pcie_attach_controller(pci_cookie);
if (ret)
pr_err("nvgpu: attaching PCIe controller failed (%d)\n", ret);
return ret;
}
static int nvgpu_pci_gpu_power_on(char *dev_name)
{
struct nvgpu_pci_power *pp;
struct nvgpu_pci_gpios *pgpios;
ktime_t time_start;
s64 time_ns;
int ret;
pp = nvgpu_pci_get_pci_power(dev_name);
if (!pp) {
pr_err("nvgpu: no pci dev by name: %s\n", dev_name);
return -ENODEV;
}
if (pp->can_pci_gc_off == false) {
pr_err("nvgpu: gc-off not enabled for pdev: %s\n", dev_name);
return -EPERM;
}
time_start = ktime_get();
nvgpu_mutex_acquire(&pp->mutex);
pgpios = &pp->gpios;
ret = nvgpu_request_pci_gpios(pgpios);
if (ret) {
pr_err("nvgpu: request pci gpios failed\n");
goto out;
}
ret = nvgpu_enable_pci_rail(pgpios);
if (ret) {
pr_err("nvgpu: enable pci rail failed\n");
goto out;
}
ret = nvgpu_assert_pci_pwr_on(pgpios);
if (ret) {
pr_err("nvgpu: assert pci pwr on failed\n");
goto out;
}
ret = nvgpu_check_pci_power_good(pgpios);
if (ret) {
pr_err("nvgpu: pci power is no good\n");
goto out;
}
ret = nvgpu_attach_pci_gpu(pp);
if (ret) {
pr_err("nvgpu: attach pci gpu failed\n");
goto out;
}
nvgpu_dump_pci_gpios(pgpios, __func__);
nvgpu_mutex_release(&pp->mutex);
time_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start));
nvgpu_update_power_on_stats(pp, time_ns);
return 0;
out:
nvgpu_mutex_release(&pp->mutex);
return ret;
}
static int nvgpu_pci_gpu_power_off(char *dev_name)
{
struct nvgpu_pci_power *pp;
struct nvgpu_pci_gpios *pgpios;
struct device *dev;
struct gk20a *g;
ktime_t time_start;
s64 time_ns;
int ret;
pp = nvgpu_pci_get_pci_power(dev_name);
if (!pp) {
pr_err("nvgpu: no pci dev by name: %s\n", dev_name);
return -ENODEV;
}
if (pp->can_pci_gc_off == false) {
pr_err("nvgpu: gc-off not enabled for pdev: %s\n", dev_name);
return -EPERM;
}
time_start = ktime_get();
nvgpu_mutex_acquire(&pp->mutex);
dev = &pp->pci_dev->dev;
g = get_gk20a(dev);
pgpios = &pp->gpios;
nvgpu_start_gpu_idle(g);
ret = nvgpu_wait_for_gpu_idle(g);
if (ret) {
pr_err("nvgpu: wait for gpu idle failed\n");
goto out;
}
ret = nvgpu_request_pci_gpios(pgpios);
if (ret) {
pr_err("nvgpu: request pci gpios failed\n");
goto out;
}
ret = nvgpu_detach_pci_gpu(pp);
if (ret) {
pr_err("nvgpu: detach pci gpu failed\n");
goto out;
}
ret = nvgpu_deassert_pci_pwr_on(pgpios);
if (ret) {
pr_err("nvgpu: deassert pci pwr on failed\n");
goto out;
}
ret = nvgpu_disable_pci_rail(pgpios);
if (ret) {
pr_err("nvgpu: disable pci rail failed\n");
goto out;
}
nvgpu_dump_pci_gpios(pgpios, __func__);
nvgpu_mutex_release(&pp->mutex);
time_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start));
nvgpu_update_power_off_stats(pp, time_ns);
return 0;
out:
nvgpu_mutex_release(&pp->mutex);
return ret;
}
int nvgpu_pci_set_powerstate(char *dev_name, int powerstate)
{
int ret = 0;
switch (powerstate) {
case NVGPU_POWER_ON:
ret = nvgpu_pci_gpu_power_on(dev_name);
break;
case NVGPU_POWER_OFF:
ret = nvgpu_pci_gpu_power_off(dev_name);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static ssize_t poweroff_store(struct device_driver *drv,
const char *buf, size_t count)
{
struct nvgpu_pci_power *pp;
char dev_name[PCI_DEV_NAME_MAX];
int ret;
ret = nvgpu_pci_parse_dev_name(buf, count, dev_name);
if (ret)
return ret;
pp = nvgpu_pci_get_pci_power(dev_name);
if (!pp)
return -ENODEV;
ret = nvgpu_pci_set_powerstate(dev_name, NVGPU_POWER_OFF);
if (ret) {
pr_err("nvgpu: GPU(%s) POWER OFF failed\n", dev_name);
return ret;
}
pr_debug("nvgpu: GPU(%s) POWER OFF done\n", dev_name);
return count;
}
static DRIVER_ATTR_WO(poweroff);
static ssize_t poweron_store(struct device_driver *drv,
const char *buf, size_t count)
{
struct nvgpu_pci_power *pp;
char dev_name[PCI_DEV_NAME_MAX];
int ret;
ret = nvgpu_pci_parse_dev_name(buf, count, dev_name);
if (ret)
return ret;
pp = nvgpu_pci_get_pci_power(dev_name);
if (!pp)
return -ENODEV;
ret = nvgpu_pci_set_powerstate(dev_name, NVGPU_POWER_ON);
if (ret) {
pr_err("nvgpu: GPU(%s) POWER ON failed\n", dev_name);
return ret;
}
pr_debug("nvgpu: GPU(%s) POWER ON done\n", dev_name);
return count;
}
static DRIVER_ATTR_WO(poweron);
int __init nvgpu_pci_power_init(struct pci_driver *nvgpu_pci_driver)
{
struct device_driver *driver = &nvgpu_pci_driver->driver;
struct dentry *d;
int ret;
ret = driver_create_file(driver, &driver_attr_probed_gpus);
if (ret)
goto err_probed_gpus;
d = debugfs_create_file("tegra_nvgpu_pci_power_stats", 0400,
NULL, NULL, &debug_power_stats_fops);
if (!d) {
ret = -ENOENT;
goto err_power_stats;
}
pci_power_stats_dbgfs_dentry = d;
ret = driver_create_file(driver, &driver_attr_poweroff);
if (ret)
goto err_poweroff;
ret = driver_create_file(driver, &driver_attr_poweron);
if (ret)
goto err_poweron;
return 0;
err_poweron:
driver_remove_file(driver, &driver_attr_poweroff);
err_poweroff:
debugfs_remove(d);
pci_power_stats_dbgfs_dentry = NULL;
err_power_stats:
driver_remove_file(driver, &driver_attr_probed_gpus);
err_probed_gpus:
return ret;
}
void __exit nvgpu_pci_power_exit(struct pci_driver *nvgpu_pci_driver)
{
struct device_driver *driver = &nvgpu_pci_driver->driver;
driver_remove_file(driver, &driver_attr_poweroff);
driver_remove_file(driver, &driver_attr_poweron);
debugfs_remove(pci_power_stats_dbgfs_dentry);
driver_remove_file(driver, &driver_attr_probed_gpus);
}
void __exit nvgpu_pci_power_cleanup(void)
{
struct nvgpu_pci_power *pp, *tmp_pp;
list_for_each_entry_safe(pp, tmp_pp, &nvgpu_pci_power_devs, list)
nvgpu_pci_remove_pci_power(pp);
}