From b556ea127e004b734b2a7bf8e67cdcf56312171d Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Mon, 17 Aug 2015 11:09:06 +0800 Subject: [PATCH] drm: add interface to get drm devices on the system v3 For mutiple GPU support, the devices on the system should be enumerated to get necessary information about each device, and the drmGetDevices interface is added for this. Currently only PCI devices are supported for the enumeration. Typical usage: int count; drmDevicePtr *foo; count = drmGetDevices(NULL, 0); foo = calloc(count, sizeof(drmDevicePtr)); count = drmGetDevices(foo, count); /* find proper device, open correct device node, etc */ drmFreeDevices(foo, count); free(foo); v2: [Jammy Zhou] - return a list of devices, rather than nodes v3: [Jammy Zhou] - fix the signed extension for PCI device info Signed-off-by: Emil Velikov Signed-off-by: Jammy Zhou --- xf86drm.c | 351 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ xf86drm.h | 34 ++++++ 2 files changed, 385 insertions(+) diff --git a/xf86drm.c b/xf86drm.c index a7cc6438..4b666125 100644 --- a/xf86drm.c +++ b/xf86drm.c @@ -55,6 +55,7 @@ #ifdef HAVE_SYS_MKDEV_H # include /* defines major(), minor(), and makedev() on Solaris */ #endif +#include /* Not all systems have MAP_FAILED defined */ #ifndef MAP_FAILED @@ -2830,3 +2831,353 @@ char *drmGetRenderDeviceNameFromFd(int fd) { return drmGetMinorNameForFD(fd, DRM_NODE_RENDER); } + +#ifdef __linux__ +static int drmParseSubsystemType(const char *str) +{ + char link[PATH_MAX + 1] = ""; + char *name; + + if (readlink(str, link, PATH_MAX) < 0) + return -EINVAL; + + name = strrchr(link, '/'); + if (!name) + return -EINVAL; + + name++; + + if (strncmp(name, "pci", 3) == 0) + return DRM_BUS_PCI; + + return -EINVAL; +} + +static int drmParsePciBusInfo(const char *str, drmPciBusInfoPtr info) +{ + int domain, bus, dev, func; + char *value; + + if (str == NULL) + return -EINVAL; + + value = strstr(str, "PCI_SLOT_NAME="); + if (value == NULL) + return -EINVAL; + + value += strlen("PCI_SLOT_NAME="); + + if (sscanf(value, "%04x:%02x:%02x.%1u", + &domain, &bus, &dev, &func) != 4) + return -EINVAL; + + info->domain = domain; + info->bus = bus; + info->dev = dev; + info->func = func; + + return 0; +} + +static int drmSameDevice(drmDevicePtr a, drmDevicePtr b) +{ + if (a->bustype != b->bustype) + return 0; + + switch (a->bustype) { + case DRM_BUS_PCI: + if (memcmp(a->businfo.pci, b->businfo.pci, sizeof(drmPciBusInfo)) == 0) + return 1; + default: + break; + } + + return 0; +} + +static int drmGetNodeType(const char *name) +{ + if (strncmp(name, DRM_PRIMARY_MINOR_NAME, + sizeof(DRM_PRIMARY_MINOR_NAME) - 1) == 0) + return DRM_NODE_PRIMARY; + + if (strncmp(name, DRM_CONTROL_MINOR_NAME, + sizeof(DRM_CONTROL_MINOR_NAME ) - 1) == 0) + return DRM_NODE_CONTROL; + + if (strncmp(name, DRM_RENDER_MINOR_NAME, + sizeof(DRM_RENDER_MINOR_NAME) - 1) == 0) + return DRM_NODE_RENDER; + + return -EINVAL; +} + +static int drmParsePciDeviceInfo(const unsigned char *config, + drmPciDeviceInfoPtr device) +{ + if (config == NULL) + return -EINVAL; + + device->vendor_id = config[0] | (config[1] << 8); + device->device_id = config[2] | (config[3] << 8); + device->revision_id = config[8]; + device->subvendor_id = config[44] | (config[45] << 8); + device->subdevice_id = config[46] | (config[47] << 8); + + return 0; +} + +static void drmFreeDevice(drmDevicePtr device) +{ + int i; + + if (device == NULL) + return; + + if (device->nodes != NULL) + for (i = 0; i < DRM_NODE_MAX; i++) + free(device->nodes[i]); + + free(device->nodes); + free(device->businfo.pci); + free(device->deviceinfo.pci); +} + +void drmFreeDevices(drmDevicePtr devices[], int count) +{ + int i; + + if (devices == NULL) + return; + + for (i = 0; i < count; i++) { + drmFreeDevice(devices[i]); + free(devices[i]); + devices[i] = NULL; + } +} + +/** + * Get drm devices on the system + * + * \param devices the array of devices with drmDevicePtr elements + * can be NULL to get the device number first + * \param max_devices the maximum number of devices for the array + * + * \return on error - negative error code, + * if devices is NULL - total number of devices available on the system, + * alternatively the number of devices stored in devices[], which is + * capped by the max_devices. + */ +int drmGetDevices(drmDevicePtr devices[], int max_devices) +{ + drmDevicePtr devs = NULL; + drmPciBusInfoPtr pcibus = NULL; + drmPciDeviceInfoPtr pcidevice = NULL; + DIR *sysdir = NULL; + struct dirent *dent = NULL; + struct stat sbuf = {0}; + char node[PATH_MAX + 1] = ""; + char path[PATH_MAX + 1] = ""; + char data[128] = ""; + unsigned char config[64] = ""; + int node_type, subsystem_type; + int maj, min; + int fd; + int ret, i = 0, j, node_count, device_count = 0; + int max_count = 16; + int *duplicated = NULL; + + devs = calloc(max_count, sizeof(*devs)); + if (devs == NULL) + return -ENOMEM; + + sysdir = opendir(DRM_DIR_NAME); + if (!sysdir) { + ret = -errno; + goto free_locals; + } + + while ((dent = readdir(sysdir))) { + node_type = drmGetNodeType(dent->d_name); + if (node_type < 0) + continue; + + snprintf(node, PATH_MAX, "%s/%s", DRM_DIR_NAME, dent->d_name); + if (stat(node, &sbuf)) + continue; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (maj != DRM_MAJOR || !S_ISCHR(sbuf.st_mode)) + continue; + + snprintf(path, PATH_MAX, "/sys/dev/char/%d:%d/device/subsystem", + maj, min); + subsystem_type = drmParseSubsystemType(path); + + if (subsystem_type < 0) + continue; + + switch (subsystem_type) { + case DRM_BUS_PCI: + pcibus = calloc(1, sizeof(*pcibus)); + if (pcibus == NULL) { + ret = -ENOMEM; + goto free_locals; + } + + snprintf(path, PATH_MAX, "/sys/dev/char/%d:%d/device/uevent", + maj, min); + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + goto free_locals; + } + ret = read(fd, data, sizeof(data)); + if (ret < 0) { + ret = -errno; + close(fd); + goto free_locals; + } + + ret = drmParsePciBusInfo(data, pcibus); + close(fd); + if (ret) + goto free_locals; + + if (i >= max_count) { + max_count += 16; + devs = realloc(devs, max_count * sizeof(*devs)); + } + + devs[i].businfo.pci = pcibus; + devs[i].bustype = subsystem_type; + devs[i].nodes = calloc(DRM_NODE_MAX, sizeof(char *)); + if (devs[i].nodes == NULL) { + ret = -ENOMEM; + goto free_locals; + } + devs[i].nodes[node_type] = strdup(node); + if (devs[i].nodes[node_type] == NULL) { + ret = -ENOMEM; + goto free_locals; + } + devs[i].available_nodes = 1 << node_type; + + if (devices != NULL) { + snprintf(path, PATH_MAX, "/sys/class/drm/%s/device/config", + dent->d_name); + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + goto free_locals; + } + ret = read(fd, config, 64); + if (ret < 0) { + ret = -errno; + close(fd); + goto free_locals; + } + + pcidevice = calloc(1, sizeof(*pcidevice)); + if (pcidevice == NULL) { + ret = -ENOMEM; + goto free_locals; + } + + ret = drmParsePciDeviceInfo(config, pcidevice); + if (ret) + goto free_locals; + + devs[i].deviceinfo.pci = pcidevice; + close(fd); + } + break; + default: + fprintf(stderr, "The subsystem type is not supported yet\n"); + break; + } + i++; + } + + node_count = i; + + /* merge duplicated devices with same domain/bus/device/func IDs */ + duplicated = calloc(node_count, sizeof(*duplicated)); + if (duplicated == NULL) { + ret = -ENOMEM; + goto free_locals; + } + + for (i = 0; i < node_count; i++) { + for (j = i+1; j < node_count; j++) { + if (duplicated[i] || duplicated[j]) + continue; + if (drmSameDevice(&devs[i], &devs[j])) { + duplicated[j] = 1; + devs[i].available_nodes |= devs[j].available_nodes; + node_type = log2(devs[j].available_nodes); + devs[i].nodes[node_type] = devs[j].nodes[node_type]; + free(devs[j].nodes); + free(devs[j].businfo.pci); + free(devs[j].deviceinfo.pci); + } + } + } + + for (i = 0; i < node_count; i++) { + if(duplicated[i] == 0) { + if ((devices != NULL) && (device_count < max_devices)) { + devices[device_count] = calloc(1, sizeof(drmDevice)); + if (devices[device_count] == NULL) { + ret = -ENOMEM; + break; + } + memcpy(devices[device_count], &devs[i], sizeof(drmDevice)); + } else + drmFreeDevice(&devs[i]); + device_count++; + } + } + + if (i < node_count) { + drmFreeDevices(devices, device_count); + for ( ; i < node_count; i++) + if(duplicated[i] == 0) + drmFreeDevice(&devs[i]); + } else + ret = device_count; + + free(duplicated); + free(devs); + closedir(sysdir); + return ret; + +free_locals: + for (j = 0; j < i; j++) + drmFreeDevice(&devs[j]); + free(pcidevice); + free(pcibus); + free(devs); + closedir(sysdir); + return ret; +} +#else +void drmFreeDevices(drmDevicePtr devices[], int count) +{ + (void)devices; + (void)count; +} + +int drmGetDevices(drmDevicePtr devices[], int max_devices) +{ + (void)devices; + (void)max_devices; + return -EINVAL; +} + +#warning "Missing implementation of drmGetDevices/drmFreeDevices" + +#endif diff --git a/xf86drm.h b/xf86drm.h index 360e04af..e82ca844 100644 --- a/xf86drm.h +++ b/xf86drm.h @@ -563,6 +563,8 @@ extern int drmOpen(const char *name, const char *busid); #define DRM_NODE_PRIMARY 0 #define DRM_NODE_CONTROL 1 #define DRM_NODE_RENDER 2 +#define DRM_NODE_MAX 3 + extern int drmOpenWithType(const char *name, const char *busid, int type); @@ -759,6 +761,38 @@ extern int drmPrimeFDToHandle(int fd, int prime_fd, uint32_t *handle); extern char *drmGetPrimaryDeviceNameFromFd(int fd); extern char *drmGetRenderDeviceNameFromFd(int fd); +#define DRM_BUS_PCI 0 + +typedef struct _drmPciBusInfo { + uint16_t domain; + uint8_t bus; + uint8_t dev; + uint8_t func; +} drmPciBusInfo, *drmPciBusInfoPtr; + +typedef struct _drmPciDeviceInfo { + uint16_t vendor_id; + uint16_t device_id; + uint16_t subvendor_id; + uint16_t subdevice_id; + uint8_t revision_id; +} drmPciDeviceInfo, *drmPciDeviceInfoPtr; + +typedef struct _drmDevice { + char **nodes; /* DRM_NODE_MAX sized array */ + int available_nodes; /* DRM_NODE_* bitmask */ + int bustype; + union { + drmPciBusInfoPtr pci; + } businfo; + union { + drmPciDeviceInfoPtr pci; + } deviceinfo; +} drmDevice, *drmDevicePtr; + +extern int drmGetDevices(drmDevicePtr devices[], int max_devices); +extern void drmFreeDevices(drmDevicePtr devices[], int count); + #if defined(__cplusplus) } #endif