#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "openbmc_intf.h" #include "gpio.h" #include "gpio_json.h" #include #include #define GPIO_PORT_OFFSET 8 #define GPIO_BASE_PATH "/sys/class/gpio" #define DEV_NAME "/dev/gpiochip0" cJSON* gpio_json = NULL; int gpio_write(GPIO* gpio, uint8_t value) { g_assert (gpio != NULL); struct gpiohandle_data data; memset(&data, 0, sizeof(data)); data.values[0] = value; if (gpio->fd <= 0) { return GPIO_ERROR; } if (ioctl(gpio->fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0) { return GPIO_WRITE_ERROR; } return GPIO_OK; } int gpio_read(GPIO* gpio, uint8_t *value) { g_assert (gpio != NULL); struct gpiohandle_data data; memset(&data, 0, sizeof(data)); if (gpio->fd <= 0) { return GPIO_ERROR; } if (ioctl(gpio->fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) < 0) { return GPIO_READ_ERROR; } *value = data.values[0]; return GPIO_OK; } /** * Determine the GPIO base number for the system. It is found in * the 'base' file in the /sys/class/gpio/gpiochipX/ directory where the * /sys/class/gpio/gpiochipX/label file has a '1e780000.gpio' in it. * * Note: This method is ASPEED specific. Could add support for * additional SOCs in the future. * * @return int - the GPIO base number, or < 0 if not found */ int get_gpio_base() { int gpio_base = -1; DIR* dir = opendir(GPIO_BASE_PATH); if (dir == NULL) { fprintf(stderr, "Unable to open directory %s\n", GPIO_BASE_PATH); return -1; } struct dirent* entry; while ((entry = readdir(dir)) != NULL) { /* Look in the gpiochip directories for a file called 'label' */ /* that contains '1e780000.gpio', then in that directory read */ /* the GPIO base out of the 'base' file. */ if (strncmp(entry->d_name, "gpiochip", 8) != 0) { continue; } gboolean is_bmc = FALSE; char* label_name; int rc = asprintf(&label_name, "%s/%s/label", GPIO_BASE_PATH, entry->d_name); if (!rc) { continue; } FILE* fd = fopen(label_name, "r"); free(label_name); if (!fd) { continue; } char label[14]; if (fgets(label, 14, fd) != NULL) { if (strcmp(label, "1e780000.gpio") == 0) { is_bmc = TRUE; } } fclose(fd); if (!is_bmc) { continue; } char* base_name; rc = asprintf(&base_name, "%s/%s/base", GPIO_BASE_PATH, entry->d_name); if (!rc) { continue; } fd = fopen(base_name, "r"); free(base_name); if (!fd) { continue; } if (fscanf(fd, "%d", &gpio_base) != 1) { gpio_base = -1; } fclose(fd); /* We found the right file. No need to continue. */ break; } closedir(dir); if (gpio_base == -1) { fprintf(stderr, "Could not find GPIO base\n"); } return gpio_base; } /** * Converts the GPIO port/offset nomenclature value * to a number. Supports the ASPEED method where * num = base + (port * 8) + offset, and the port is an * A-Z character that converts to 0 to 25. The base * is obtained form the hardware. * * For example: "A5" -> 5, "Z7" -> 207 * * @param[in] gpio - the GPIO name * * @return int - the GPIO number or < 0 if not found */ int convert_gpio_to_num(const char* gpio) { size_t len = strlen(gpio); if (len < 2) { fprintf(stderr, "Invalid GPIO name %s\n", gpio); return -1; } /* Read the offset from the last character */ if (!isdigit(gpio[len-1])) { fprintf(stderr, "Invalid GPIO offset in GPIO %s\n", gpio); return -1; } int offset = gpio[len-1] - '0'; /* Read the port from the second to last character */ if (!isalpha(gpio[len-2])) { fprintf(stderr, "Invalid GPIO port in GPIO %s\n", gpio); return -1; } int port = toupper(gpio[len-2]) - 'A'; /* Check for a 2 character port, like AA */ if ((len == 3) && isalpha(gpio[len-3])) { port += 26 * (toupper(gpio[len-3]) - 'A' + 1); } return (port * GPIO_PORT_OFFSET) + offset; } /** * Returns the cJSON pointer to the GPIO definition * for the GPIO passed in. * * @param[in] gpio_name - the GPIO name, like BMC_POWER_UP * * @return cJSON* - pointer to the cJSON object or NULL * if not found. */ cJSON* get_gpio_def(const char* gpio_name) { if (gpio_json == NULL) { gpio_json = load_json(); if (gpio_json == NULL) { return NULL; } } cJSON* gpio_defs = cJSON_GetObjectItem(gpio_json, "gpio_definitions"); g_assert(gpio_defs != NULL); cJSON* def; cJSON_ArrayForEach(def, gpio_defs) { cJSON* name = cJSON_GetObjectItem(def, "name"); g_assert(name != NULL); if (strcmp(name->valuestring, gpio_name) == 0) { return def; } } return NULL; } /** * Frees the gpio_json memory * * Can be called once when callers are done calling making calls * to gpio_init() so that the JSON only needs to be loaded once. */ void gpio_inits_done() { if (gpio_json != NULL) { cJSON_Delete(gpio_json); gpio_json = NULL; } } /** * Fills in the dev, direction, and num elements in * the GPIO structure. * * @param gpio - the GPIO structure to fill in * * @return GPIO_OK if successful */ int gpio_get_params(GPIO* gpio) { gpio->dev = g_strdup(GPIO_BASE_PATH); const cJSON* def = get_gpio_def(gpio->name); if (def == NULL) { fprintf(stderr, "Unable to find GPIO %s in the JSON\n", gpio->name); return GPIO_LOOKUP_ERROR; } const cJSON* dir = cJSON_GetObjectItem(def, "direction"); g_assert(dir != NULL); gpio->direction = g_strdup(dir->valuestring); /* Must use either 'num', like 87, or 'pin', like "A5" */ const cJSON* num = cJSON_GetObjectItem(def, "num"); if ((num != NULL) && cJSON_IsNumber(num)) { // When the "num" key is used, we will assume // that the system has gpiochip0 and that each // bank has the same amount of pins struct gpiochip_info info; int fd, ret; // Open the device fd = open(DEV_NAME, O_RDONLY); if (fd < 0) { fprintf(stderr, "Unable to open %s: %s\n", DEV_NAME, strerror(errno)); return GPIO_LOOKUP_ERROR; } // Query GPIO chip info ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); if (ret == -1) { fprintf(stderr, "Unable to get chip info from ioctl: %s\n", strerror(errno)); close(fd); return GPIO_LOOKUP_ERROR; } close(fd); printf("Number of pins per bank: %d\n", info.lines); gpio->num = (num->valueint) % info.lines; gpio->chip_id = (num->valueint) / info.lines; } else { const cJSON* pin = cJSON_GetObjectItem(def, "pin"); g_assert(pin != NULL); int pin_id = convert_gpio_to_num(pin->valuestring); if (pin_id < 0) { return GPIO_LOOKUP_ERROR; } gpio->num = (size_t) pin_id; } // TODO: For the purposes of skeleton and the projects that use it, // it should be safe to assume this will always be 0. Eventually skeleton // should be going away, but if the need arises before then this may need // to be updated to handle non-zero cases. gpio->chip_id = 0; return GPIO_OK; } int gpio_open(GPIO* gpio, uint8_t default_value) { g_assert (gpio != NULL); char buf[255]; sprintf(buf, "/dev/gpiochip%d", gpio->chip_id); gpio->fd = open(buf, 0); if (gpio->fd == -1) { return GPIO_OPEN_ERROR; } struct gpiohandle_request req; memset(&req, 0, sizeof(req)); strncpy(req.consumer_label, "skeleton-gpio", sizeof(req.consumer_label)); // open gpio for writing or reading if (gpio->direction == NULL) { gpio_close(gpio); return GPIO_OPEN_ERROR; } req.flags = (strcmp(gpio->direction,"in") == 0) ? GPIOHANDLE_REQUEST_INPUT : GPIOHANDLE_REQUEST_OUTPUT; req.lineoffsets[0] = gpio->num; req.lines = 1; if (strcmp(gpio->direction,"out") == 0) { req.default_values[0] = default_value; } int rc = ioctl(gpio->fd, GPIO_GET_LINEHANDLE_IOCTL, &req); if (rc < 0) { gpio_close(gpio); return GPIO_OPEN_ERROR; } gpio_close(gpio); gpio->fd = req.fd; return GPIO_OK; } void gpio_close(GPIO* gpio) { if(gpio->fd < 0) return; close(gpio->fd); gpio->fd = -1; }