blob: e7c67dae3a13959661dfe21aee8d2c5b234b1140 [file] [log] [blame]
/*
* 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"
/**
* 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 total = 0;
do {
int ret = read(fd, buf + total, len - total);
if (ret < 0 && (errno == EINTR || errno == EAGAIN))
continue;
if (ret <= 0)
return total ?: ret;
total += ret;
if (total == len)
return total;
} while (1);
}
static int writeN(int fd, const char *buf, int len)
{
int total = 0;
do {
int ret = write(fd, buf + total, len - total);
if (ret < 0 && (errno == EINTR || errno == EAGAIN))
continue;
if (ret <= 0)
return total ?: ret;
total += ret;
if (total == len)
return total;
} while (1);
}
/**
* igt_sysfs_path:
* @device: fd of the device (or -1 to default to Intel)
* @path: buffer to fill with the sysfs path to the device
* @pathlen: length of @path buffer
* @idx: optional pointer to store the card index of the opened device
*
* 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, int *idx)
{
struct stat st;
if (device != -1 && (fstat(device, &st) || !S_ISCHR(st.st_mode)))
return NULL;
for (int n = 0; n < 16; n++) {
int len = snprintf(path, pathlen, "/sys/class/drm/card%d", n);
if (device != -1) {
FILE *file;
int ret, maj, min;
sprintf(path + len, "/dev");
file = fopen(path, "r");
if (!file)
continue;
ret = fscanf(file, "%d:%d", &maj, &min);
fclose(file);
if (ret != 2 ||
major(st.st_rdev) != maj ||
minor(st.st_rdev) != min)
continue;
} else {
/* Bleh. Search for intel */
sprintf(path + len, "/error");
if (stat(path, &st))
continue;
}
path[len] = '\0';
if (idx)
*idx = n;
return path;
}
return NULL;
}
/**
* igt_sysfs_open:
* @device: fd of the device (or -1 to default to Intel)
* @idx: optional pointer to store the card index of the opened 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, int *idx)
{
char path[80];
if (!igt_sysfs_path(device, path, sizeof(path), idx))
return -1;
return open(path, O_RDONLY);
}
/**
* igt_sysfs_set_parameters:
* @device: fd of the device (or -1 to default to Intel)
* @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 (or -1 to default to Intel)
*
* 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;
dir = igt_sysfs_open(device, &params);
if (dir < 0)
return -1;
params = -1;
//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 -1 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 false;
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, -1 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 false;
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 != -1)
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);
}
close(fd);
return ret;
}
int igt_sysfs_vprintf(int dir, const char *attr, const char *fmt, va_list ap)
{
FILE *file;
int fd;
int ret = -1;
fd = openat(dir, attr, O_WRONLY);
if (fd < 0)
return -1;
file = fdopen(fd, "w");
if (file) {
ret = vfprintf(file, fmt, ap);
fclose(file);
}
close(fd);
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;
}
/**
* kick_fbcon:
* @enable: boolean value
*
* This functions enables/disables the text console running on top of the
* framebuffer device.
*/
void kick_fbcon(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, enable ? "dummy device" :
"frame buffer device"))
continue;
sprintf(buf, "%s/%s/bind", path, de->d_name);
fd = open(buf, O_WRONLY);
if (fd != -1) {
igt_ignore_warn(write(fd, "0\n", 2));
close(fd);
}
}
closedir(dir);
}
/**
* 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);
}