/*
 * Copyright © 2013, 2015 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Authors:
 *    Paulo Zanoni <paulo.r.zanoni@intel.com>
 *    David Weinehall <david.weinehall@intel.com>
 *
 */
#include <fcntl.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "drmtest.h"
#include "igt_pm.h"
#include "igt_aux.h"

/**
 * SECTION:igt_pm
 * @short_description: Power Management related helpers
 * @title: Power Management
 * @include: igt.h
 *
 * This library provides various helpers to enable power management for,
 * and in some cases subsequently allow restoring the old behaviour of,
 * various external components that by default are set up in a way
 * that interferes with the testing of our power management functionality.
 */

enum {
	POLICY_UNKNOWN = -1,
	POLICY_MAX_PERFORMANCE = 0,
	POLICY_MEDIUM_POWER = 1,
	POLICY_MIN_POWER = 2
};

#define MAX_PERFORMANCE_STR	"max_performance\n"
#define MEDIUM_POWER_STR	"medium_power\n"
#define MIN_POWER_STR		"min_power\n"
/* Remember to fix this if adding longer strings */
#define MAX_POLICY_STRLEN	strlen(MAX_PERFORMANCE_STR)

static char __igt_pm_audio_runtime_power_save[64];
static char __igt_pm_audio_runtime_control[64];

static void __igt_pm_audio_runtime_exit_handler(int sig)
{
	int fd;

	igt_debug("Restoring audio power management to '%s' and '%s'\n",
		  __igt_pm_audio_runtime_power_save,
		  __igt_pm_audio_runtime_control);

	fd = open("/sys/module/snd_hda_intel/parameters/power_save", O_WRONLY);
	if (fd < 0)
		return;
	if (write(fd, __igt_pm_audio_runtime_power_save,
		  strlen(__igt_pm_audio_runtime_power_save)) !=
	    strlen(__igt_pm_audio_runtime_power_save))
		igt_warn("Failed to restore audio power_save to '%s'\n",
			 __igt_pm_audio_runtime_power_save);
	close(fd);

	fd = open("/sys/bus/pci/devices/0000:00:03.0/power/control", O_WRONLY);
	if (fd < 0)
		return;
	if (write(fd, __igt_pm_audio_runtime_control,
		  strlen(__igt_pm_audio_runtime_control)) !=
	    strlen(__igt_pm_audio_runtime_control))
		igt_warn("Failed to restore audio control to '%s'\n",
			 __igt_pm_audio_runtime_control);
	close(fd);
}

static void strchomp(char *str)
{
	int len = strlen(str);

	if (len && str[len - 1] == '\n')
		str[len - 1] = 0;
}

/**
 * igt_pm_enable_audio_runtime_pm:
 *
 * We know that if we don't enable audio runtime PM, snd_hda_intel will never
 * release its power well refcount, and we'll never reach the LPSP state.
 * There's no guarantee that it will release the power well if we enable
 * runtime PM, but at least we can try.
 *
 * We don't have any assertions on open since the user may not even have
 * snd_hda_intel loaded, which is not a problem.
 */
void igt_pm_enable_audio_runtime_pm(void)
{
	int fd;

	/* Check if already enabled. */
	if (__igt_pm_audio_runtime_power_save[0])
		return;

	fd = open("/sys/module/snd_hda_intel/parameters/power_save", O_RDWR);
	if (fd >= 0) {
		igt_assert(read(fd, __igt_pm_audio_runtime_power_save,
				sizeof(__igt_pm_audio_runtime_power_save)) > 0);
		strchomp(__igt_pm_audio_runtime_power_save);
		igt_install_exit_handler(__igt_pm_audio_runtime_exit_handler);
		igt_assert_eq(write(fd, "1\n", 2), 2);
		close(fd);
	}
	fd = open("/sys/bus/pci/devices/0000:00:03.0/power/control", O_RDWR);
	if (fd >= 0) {
		igt_assert(read(fd, __igt_pm_audio_runtime_control,
				sizeof(__igt_pm_audio_runtime_control)) > 0);
		strchomp(__igt_pm_audio_runtime_control);
		igt_assert_eq(write(fd, "auto\n", 5), 5);
		close(fd);
	}

	igt_debug("Saved audio power management as '%s' and '%s'\n",
		  __igt_pm_audio_runtime_power_save,
		  __igt_pm_audio_runtime_control);

	/* Give some time for it to react. */
	sleep(1);
}

/**
 * igt_pm_enable_sata_link_power_management:
 *
 * Enable the min_power policy for SATA link power management.
 * Without this we cannot reach deep runtime power states.
 *
 * We don't have any assertions on open since the system might not have
 * a SATA host.
 *
 * Returns:
 * An opaque pointer to the data needed to restore the default values
 * after the test has terminated, or NULL if SATA link power management
 * is not supported. This pointer should be freed when no longer used
 * (typically after having called restore_sata_link_power_management()).
 */
int8_t *igt_pm_enable_sata_link_power_management(void)
{
	int fd, i;
	ssize_t len;
	char *buf;
	char *file_name;
	int8_t *link_pm_policies = NULL;

	file_name = malloc(PATH_MAX);
	buf = malloc(MAX_POLICY_STRLEN + 1);

	for (i = 0; ; i++) {
		int8_t policy;

		snprintf(file_name, PATH_MAX,
			 "/sys/class/scsi_host/host%d/link_power_management_policy",
			 i);

		fd = open(file_name, O_RDWR);
		if (fd < 0)
			break;

		len = read(fd, buf, MAX_POLICY_STRLEN);
		buf[len] = '\0';

		if (!strncmp(MAX_PERFORMANCE_STR, buf,
			     strlen(MAX_PERFORMANCE_STR)))
			policy = POLICY_MAX_PERFORMANCE;
		else if (!strncmp(MEDIUM_POWER_STR, buf,
				  strlen(MEDIUM_POWER_STR)))
			policy = POLICY_MEDIUM_POWER;
		else if (!strncmp(MIN_POWER_STR, buf,
				  strlen(MIN_POWER_STR)))
			policy = POLICY_MIN_POWER;
		else
			policy = POLICY_UNKNOWN;

		if (!(i % 256))
			link_pm_policies = realloc(link_pm_policies,
						   (i / 256 + 1) * 256 + 1);

		link_pm_policies[i] = policy;
		link_pm_policies[i + 1] = 0;

		/* If the policy is something we don't know about,
		 * don't touch it, since we might potentially break things.
		 * And we obviously don't need to touch anything if the
		 * setting is already correct...
		 */
		if (policy != POLICY_UNKNOWN &&
		    policy != POLICY_MIN_POWER) {
			lseek(fd, 0, SEEK_SET);
			igt_assert_eq(write(fd, MIN_POWER_STR,
					    strlen(MIN_POWER_STR)),
				      strlen(MIN_POWER_STR));
		}
		close(fd);
	}
	free(buf);
	free(file_name);

	return link_pm_policies;
}

/**
 * igt_pm_restore_sata_link_power_management:
 * @pm_data: An opaque pointer with saved link PM policies;
 *           If NULL is passed we force enable the "max_performance" policy.
 *
 * Restore the link power management policies to the values
 * prior to enabling min_power.
 *
 * Caveat: If the system supports hotplugging and hotplugging takes
 *         place during our testing so that the hosts change numbers
 *         we might restore the settings to the wrong hosts.
 */
void igt_pm_restore_sata_link_power_management(int8_t *pm_data)

{
	int fd, i;
	char *file_name;

	/* Disk runtime PM policies. */
	file_name = malloc(PATH_MAX);
	for (i = 0; ; i++) {
		int8_t policy;

		if (!pm_data)
			policy = POLICY_MAX_PERFORMANCE;
		else if (pm_data[i] == POLICY_UNKNOWN)
			continue;
		else
			policy = pm_data[i];

		snprintf(file_name, PATH_MAX,
			 "/sys/class/scsi_host/host%d/link_power_management_policy",
			 i);

		fd = open(file_name, O_WRONLY);
		if (fd < 0)
			break;

		switch (policy) {
		default:
		case POLICY_MAX_PERFORMANCE:
			igt_assert_eq(write(fd, MAX_PERFORMANCE_STR,
					    strlen(MAX_PERFORMANCE_STR)),
				      strlen(MAX_PERFORMANCE_STR));
			break;

		case POLICY_MEDIUM_POWER:
			igt_assert_eq(write(fd, MEDIUM_POWER_STR,
					    strlen(MEDIUM_POWER_STR)),
				      strlen(MEDIUM_POWER_STR));
			break;

		case POLICY_MIN_POWER:
			igt_assert_eq(write(fd, MIN_POWER_STR,
					    strlen(MIN_POWER_STR)),
				      strlen(MIN_POWER_STR));
			break;
		}

		close(fd);
	}
	free(file_name);
}
#define POWER_DIR "/sys/devices/pci0000:00/0000:00:02.0/power"
/* We just leak this on exit ... */
int pm_status_fd = -1;

static char __igt_pm_runtime_autosuspend[64];
static char __igt_pm_runtime_control[64];

static void __igt_pm_runtime_exit_handler(int sig)
{
	int fd;

	igt_debug("Restoring runtime management to '%s' and '%s'\n",
		  __igt_pm_runtime_autosuspend,
		  __igt_pm_runtime_control);

	fd = open(POWER_DIR "/autosuspend_delay_ms", O_WRONLY);
	if (fd < 0)
		return;
	if (write(fd, __igt_pm_runtime_autosuspend,
		  strlen(__igt_pm_runtime_autosuspend)) !=
	    strlen(__igt_pm_runtime_autosuspend))
		igt_warn("Failed to restore runtime pm autosuspend delay to '%s'\n",
			 __igt_pm_runtime_autosuspend);
	close(fd);

	fd = open(POWER_DIR "/control", O_WRONLY);
	if (fd < 0)
		return;
	if (write(fd, __igt_pm_runtime_control,
		  strlen(__igt_pm_runtime_control)) !=
	    strlen(__igt_pm_runtime_control))
		igt_warn("Failed to restore runtime pm control to '%s'\n",
			 __igt_pm_runtime_control);
	close(fd);
}

/**
 * igt_setup_runtime_pm:
 *
 * Sets up the runtime PM helper functions and enables runtime PM. To speed up
 * tests the autosuspend delay is set to 0.
 *
 * Returns:
 * True if runtime pm is available, false otherwise.
 */
bool igt_setup_runtime_pm(void)
{
	int fd;
	ssize_t size;
	char buf[6];

	if (pm_status_fd >= 0)
		return true;

	igt_pm_enable_audio_runtime_pm();

	/* Our implementation uses autosuspend. Try to set it to 0ms so the test
	 * suite goes faster and we have a higher probability of triggering race
	 * conditions. */
	fd = open(POWER_DIR "/autosuspend_delay_ms", O_RDWR);
	igt_assert_f(fd >= 0,
		     "Can't open " POWER_DIR "/autosuspend_delay_ms\n");

	/*
	 * Save previous values to be able to  install exit handler to restore
	 * them on test exit.
	 */
	size = read(fd, __igt_pm_runtime_autosuspend,
		    sizeof(__igt_pm_runtime_autosuspend));

	/*
	 * If we fail to read from the file, it means this system doesn't
	 * support runtime PM.
	 */
	if (size <= 0) {
		close(fd);
		return false;
	}

	strchomp(__igt_pm_runtime_autosuspend);
	igt_install_exit_handler(__igt_pm_runtime_exit_handler);

	size = write(fd, "0\n", 2);

	close(fd);

	if (size != 2)
		return false;

	/* We know we support runtime PM, let's try to enable it now. */
	fd = open(POWER_DIR "/control", O_RDWR);
	igt_assert_f(fd >= 0, "Can't open " POWER_DIR "/control\n");

	igt_assert(read(fd, __igt_pm_runtime_control,
			sizeof(__igt_pm_runtime_control)) > 0);
	strchomp(__igt_pm_runtime_control);

	igt_debug("Saved runtime power management as '%s' and '%s'\n",
		  __igt_pm_runtime_autosuspend, __igt_pm_runtime_control);

	size = write(fd, "auto\n", 5);
	igt_assert(size == 5);

	lseek(fd, 0, SEEK_SET);
	size = read(fd, buf, ARRAY_SIZE(buf));
	igt_assert(size == 5);
	igt_assert(strncmp(buf, "auto\n", 5) == 0);

	close(fd);

	pm_status_fd = open(POWER_DIR "/runtime_status", O_RDONLY);
	igt_assert_f(pm_status_fd >= 0,
		     "Can't open " POWER_DIR "/runtime_status\n");

	return true;
}

/**
 * igt_get_runtime_pm_status:
 *
 * Returns: The current runtime PM status.
 */
enum igt_runtime_pm_status igt_get_runtime_pm_status(void)
{
	ssize_t n_read;
	char buf[32];

	lseek(pm_status_fd, 0, SEEK_SET);
	n_read = read(pm_status_fd, buf, ARRAY_SIZE(buf));
	igt_assert(n_read >= 0);
	buf[n_read] = '\0';

	if (strncmp(buf, "suspended\n", n_read) == 0)
		return IGT_RUNTIME_PM_STATUS_SUSPENDED;
	else if (strncmp(buf, "active\n", n_read) == 0)
		return IGT_RUNTIME_PM_STATUS_ACTIVE;
	else if (strncmp(buf, "suspending\n", n_read) == 0)
		return IGT_RUNTIME_PM_STATUS_SUSPENDING;
	else if (strncmp(buf, "resuming\n", n_read) == 0)
		return IGT_RUNTIME_PM_STATUS_RESUMING;

	igt_assert_f(false, "Unknown status %s\n", buf);
	return IGT_RUNTIME_PM_STATUS_UNKNOWN;
}

/**
 * igt_wait_for_pm_status:
 * @status: desired runtime PM status
 *
 * Waits until for the driver to switch to into the desired runtime PM status,
 * with a 10 second timeout.
 *
 * Returns:
 * True if the desired runtime PM status was attained, false if the operation
 * timed out.
 */
bool igt_wait_for_pm_status(enum igt_runtime_pm_status status)
{
	return igt_wait(igt_get_runtime_pm_status() == status, 10000, 100);
}
