You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

656 lines
14 KiB

/*
* Copyright © 2016 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.
*
*/
#include <inttypes.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/mount.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <i915_drm.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include "igt_core.h"
#include "igt_sysfs.h"
#include "igt_device.h"
/**
* SECTION:igt_sysfs
* @short_description: Support code for sysfs features
* @title: sysfs
* @include: igt.h
*
* This library provides helpers to access sysfs features. Right now it only
* provides basic support for like igt_sysfs_open().
*/
static int readN(int fd, char *buf, int len)
{
int ret, total = 0;
do {
ret = read(fd, buf + total, len - total);
if (ret < 0)
ret = -errno;
if (ret == -EINTR || ret == -EAGAIN)
continue;
if (ret <= 0)
break;
total += ret;
} while (total != len);
return total ?: ret;
}
static int writeN(int fd, const char *buf, int len)
{
int ret, total = 0;
do {
ret = write(fd, buf + total, len - total);
if (ret < 0)
ret = -errno;
if (ret == -EINTR || ret == -EAGAIN)
continue;
if (ret <= 0)
break;
total += ret;
} while (total != len);
return total ?: ret;
}
/**
* igt_sysfs_path:
* @device: fd of the device
* @path: buffer to fill with the sysfs path to the device
* @pathlen: length of @path buffer
*
* This finds the sysfs directory corresponding to @device.
*
* Returns:
* The directory path, or NULL on failure.
*/
char *igt_sysfs_path(int device, char *path, int pathlen)
{
struct stat st;
if (device < 0)
return NULL;
if (fstat(device, &st) || !S_ISCHR(st.st_mode))
return NULL;
snprintf(path, pathlen, "/sys/dev/char/%d:%d",
major(st.st_rdev), minor(st.st_rdev));
if (access(path, F_OK))
return NULL;
return path;
}
/**
* igt_sysfs_open:
* @device: fd of the device
*
* This opens the sysfs directory corresponding to device for use
* with igt_sysfs_set() and igt_sysfs_get().
*
* Returns:
* The directory fd, or -1 on failure.
*/
int igt_sysfs_open(int device)
{
char path[80];
if (!igt_sysfs_path(device, path, sizeof(path)))
return -1;
return open(path, O_RDONLY);
}
/**
* igt_sysfs_set_parameters:
* @device: fd of the device
* @parameter: the name of the parameter to set
* @fmt: printf-esque format string
*
* Returns true on success
*/
bool igt_sysfs_set_parameter(int device,
const char *parameter,
const char *fmt, ...)
{
va_list ap;
int dir;
int ret;
dir = igt_sysfs_open_parameters(device);
if (dir < 0)
return false;
va_start(ap, fmt);
ret = igt_sysfs_vprintf(dir, parameter, fmt, ap);
va_end(ap);
close(dir);
return ret > 0;
}
/**
* igt_sysfs_open_parameters:
* @device: fd of the device
*
* This opens the module parameters directory (under sysfs) corresponding
* to the device for use with igt_sysfs_set() and igt_sysfs_get().
*
* Returns:
* The directory fd, or -1 on failure.
*/
int igt_sysfs_open_parameters(int device)
{
int dir, params = -1;
dir = igt_sysfs_open(device);
if (dir >= 0) {
params = openat(dir,
"device/driver/module/parameters",
O_RDONLY);
close(dir);
}
if (params < 0) { /* builtin? */
drm_version_t version;
char name[32] = "";
char path[PATH_MAX];
memset(&version, 0, sizeof(version));
version.name_len = sizeof(name);
version.name = name;
ioctl(device, DRM_IOCTL_VERSION, &version);
sprintf(path, "/sys/module/%s/parameters", name);
params = open(path, O_RDONLY);
}
return params;
}
/**
* igt_sysfs_write:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
* @data: the block to write from
* @len: the length to write
*
* This writes @len bytes from @data to the sysfs file.
*
* Returns:
* The number of bytes written, or -errno on error.
*/
int igt_sysfs_write(int dir, const char *attr, const void *data, int len)
{
int fd;
fd = openat(dir, attr, O_WRONLY);
if (fd < 0)
return -errno;
len = writeN(fd, data, len);
close(fd);
return len;
}
/**
* igt_sysfs_read:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
* @data: the block to read into
* @len: the maximum length to read
*
* This reads @len bytes from the sysfs file to @data
*
* Returns:
* The length read, -errno on failure.
*/
int igt_sysfs_read(int dir, const char *attr, void *data, int len)
{
int fd;
fd = openat(dir, attr, O_RDONLY);
if (fd < 0)
return -errno;
len = readN(fd, data, len);
close(fd);
return len;
}
/**
* igt_sysfs_set:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
* @value: the string to write
*
* This writes the value to the sysfs file.
*
* Returns:
* True on success, false on failure.
*/
bool igt_sysfs_set(int dir, const char *attr, const char *value)
{
int len = strlen(value);
return igt_sysfs_write(dir, attr, value, len) == len;
}
/**
* igt_sysfs_get:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
*
* This reads the value from the sysfs file.
*
* Returns:
* A nul-terminated string, must be freed by caller after use, or NULL
* on failure.
*/
char *igt_sysfs_get(int dir, const char *attr)
{
char *buf;
int len, offset, rem;
int ret, fd;
fd = openat(dir, attr, O_RDONLY);
if (fd < 0)
return NULL;
offset = 0;
len = 64;
rem = len - offset - 1;
buf = malloc(len);
if (!buf)
goto out;
while ((ret = readN(fd, buf + offset, rem)) == rem) {
char *newbuf;
newbuf = realloc(buf, 2*len);
if (!newbuf)
break;
buf = newbuf;
len *= 2;
offset += ret;
rem = len - offset - 1;
}
if (ret > 0)
offset += ret;
buf[offset] = '\0';
while (offset > 0 && buf[offset-1] == '\n')
buf[--offset] = '\0';
out:
close(fd);
return buf;
}
/**
* igt_sysfs_scanf:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
* @fmt: scanf format string
* @...: Additional paramaters to store the scaned input values
*
* scanf() wrapper for sysfs.
*
* Returns:
* Number of values successfully scanned (which can be 0), EOF on errors or
* premature end of file.
*/
int igt_sysfs_scanf(int dir, const char *attr, const char *fmt, ...)
{
FILE *file;
int fd;
int ret = -1;
fd = openat(dir, attr, O_RDONLY);
if (fd < 0)
return -1;
file = fdopen(fd, "r");
if (file) {
va_list ap;
va_start(ap, fmt);
ret = vfscanf(file, fmt, ap);
va_end(ap);
fclose(file);
} else {
close(fd);
}
return ret;
}
int igt_sysfs_vprintf(int dir, const char *attr, const char *fmt, va_list ap)
{
char stack[128], *buf = stack;
va_list tmp;
int ret, fd;
fd = openat(dir, attr, O_WRONLY);
if (fd < 0)
return -errno;
va_copy(tmp, ap);
ret = vsnprintf(buf, sizeof(stack), fmt, tmp);
va_end(tmp);
if (ret < 0)
return -EINVAL;
if (ret > sizeof(stack)) {
unsigned int len = ret + 1;
buf = malloc(len);
if (!buf)
return -ENOMEM;
ret = vsnprintf(buf, ret, fmt, ap);
if (ret > len) {
free(buf);
return -EINVAL;
}
}
ret = writeN(fd, buf, ret);
close(fd);
if (buf != stack)
free(buf);
return ret;
}
/**
* igt_sysfs_printf:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
* @fmt: printf format string
* @...: Additional paramaters to store the scaned input values
*
* printf() wrapper for sysfs.
*
* Returns:
* Number of characters written, negative value on error.
*/
int igt_sysfs_printf(int dir, const char *attr, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = igt_sysfs_vprintf(dir, attr, fmt, ap);
va_end(ap);
return ret;
}
/**
* igt_sysfs_get_u32:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
*
* Convenience wrapper to read a unsigned 32bit integer from a sysfs file.
*
* Returns:
* The value read.
*/
uint32_t igt_sysfs_get_u32(int dir, const char *attr)
{
uint32_t result;
if (igt_sysfs_scanf(dir, attr, "%u", &result) != 1)
return 0;
return result;
}
/**
* igt_sysfs_set_u32:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
* @value: value to set
*
* Convenience wrapper to write a unsigned 32bit integer to a sysfs file.
*
* Returns:
* True if successfully written
*/
bool igt_sysfs_set_u32(int dir, const char *attr, uint32_t value)
{
return igt_sysfs_printf(dir, attr, "%u", value) > 0;
}
/**
* igt_sysfs_get_boolean:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
*
* Convenience wrapper to read a boolean sysfs file.
*
* Returns:
* The value read.
*/
bool igt_sysfs_get_boolean(int dir, const char *attr)
{
int result;
if (igt_sysfs_scanf(dir, attr, "%d", &result) != 1)
return false;
return result;
}
/**
* igt_sysfs_set_boolean:
* @dir: directory for the device from igt_sysfs_open()
* @attr: name of the sysfs node to open
* @value: value to set
*
* Convenience wrapper to write a boolean sysfs file.
*
* Returns:
* The value read.
*/
bool igt_sysfs_set_boolean(int dir, const char *attr, bool value)
{
return igt_sysfs_printf(dir, attr, "%d", value) == 1;
}
static void bind_con(const char *name, bool enable)
{
const char *path = "/sys/class/vtconsole";
DIR *dir;
struct dirent *de;
dir = opendir(path);
if (!dir)
return;
while ((de = readdir(dir))) {
char buf[PATH_MAX];
int fd, len;
if (strncmp(de->d_name, "vtcon", 5))
continue;
sprintf(buf, "%s/%s/name", path, de->d_name);
fd = open(buf, O_RDONLY);
if (fd < 0)
continue;
buf[sizeof(buf) - 1] = '\0';
len = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (len >= 0)
buf[len] = '\0';
if (!strstr(buf, name))
continue;
sprintf(buf, "%s/%s/bind", path, de->d_name);
fd = open(buf, O_WRONLY);
if (fd != -1) {
igt_ignore_warn(write(fd, enable ? "1\n" : "0\n", 2));
close(fd);
}
break;
}
closedir(dir);
}
/**
* bind_fbcon:
* @enable: boolean value
*
* This functions enables/disables the text console running on top of the
* framebuffer device.
*/
void bind_fbcon(bool enable)
{
/*
* The vtcon bind interface seems somewhat broken. Possibly
* depending on the order the console drivers have been
* registered you either have to unbind the old driver,
* or bind the new driver. Let's do both.
*/
bind_con("dummy device", !enable);
bind_con("frame buffer device", enable);
}
/**
* kick_snd_hda_intel:
*
* This functions unbinds the snd_hda_intel driver so the module cand be
* unloaded.
*
*/
void kick_snd_hda_intel(void)
{
DIR *dir;
struct dirent *snd_hda;
int fd; size_t len;
const char *dpath = "/sys/bus/pci/drivers/snd_hda_intel";
const char *path = "/sys/bus/pci/drivers/snd_hda_intel/unbind";
const char *devid = "0000:";
fd = open(path, O_WRONLY);
if (fd < 0) {
return;
}
dir = opendir(dpath);
if (!dir)
goto out;
len = strlen(devid);
while ((snd_hda = readdir(dir))) {
struct stat st;
char fpath[PATH_MAX];
if (*snd_hda->d_name == '.')
continue;
snprintf(fpath, sizeof(fpath), "%s/%s", dpath, snd_hda->d_name);
if (lstat(fpath, &st))
continue;
if (!S_ISLNK(st.st_mode))
continue;
if (!strncmp(devid, snd_hda->d_name, len)) {
igt_ignore_warn(write(fd, snd_hda->d_name,
strlen(snd_hda->d_name)));
}
}
closedir(dir);
out:
close(fd);
}
static int fbcon_cursor_blink_fd = -1;
static char fbcon_cursor_blink_prev_value[2];
static void fbcon_cursor_blink_restore(int sig)
{
write(fbcon_cursor_blink_fd, fbcon_cursor_blink_prev_value,
strlen(fbcon_cursor_blink_prev_value) + 1);
close(fbcon_cursor_blink_fd);
}
/**
* fbcon_blink_enable:
* @enable: if true enables the fbcon cursor blinking otherwise disables it
*
* Enables or disables the cursor blinking in fbcon, it also restores the
* previous blinking state when exiting test.
*
*/
void fbcon_blink_enable(bool enable)
{
const char *cur_blink_path = "/sys/class/graphics/fbcon/cursor_blink";
int fd, r;
char buffer[2];
fd = open(cur_blink_path, O_RDWR);
igt_require(fd >= 0);
/* Restore original value on exit */
if (fbcon_cursor_blink_fd == -1) {
r = read(fd, fbcon_cursor_blink_prev_value,
sizeof(fbcon_cursor_blink_prev_value));
if (r > 0) {
fbcon_cursor_blink_fd = dup(fd);
igt_assert(fbcon_cursor_blink_fd >= 0);
igt_install_exit_handler(fbcon_cursor_blink_restore);
}
}
r = snprintf(buffer, sizeof(buffer), enable ? "1" : "0");
write(fd, buffer, r + 1);
close(fd);
}