diff --git a/src/kernel/core/Jamfile b/src/kernel/core/Jamfile index ca6ed41485..a91e5c5ca5 100644 --- a/src/kernel/core/Jamfile +++ b/src/kernel/core/Jamfile @@ -1,10 +1,11 @@ SubDir OBOS_TOP src kernel core ; KernelObjects - <$(SOURCE_GRIST)>cbuf.c - <$(SOURCE_GRIST)>console.c - <$(SOURCE_GRIST)>cpu.c - <$(SOURCE_GRIST)>debug.c + <$(SOURCE_GRIST)>cbuf.c + <$(SOURCE_GRIST)>console.c + <$(SOURCE_GRIST)>cpu.c + <$(SOURCE_GRIST)>debug.c + <$(SOURCE_GRIST)>driver_settings.c <$(SOURCE_GRIST)>elf.c <$(SOURCE_GRIST)>faults.c <$(SOURCE_GRIST)>fd.c diff --git a/src/kernel/core/driver_settings.c b/src/kernel/core/driver_settings.c index dee5ee6279..b42a5f2b1d 100644 --- a/src/kernel/core/driver_settings.c +++ b/src/kernel/core/driver_settings.c @@ -5,23 +5,471 @@ */ +#include #include +// this definition is currently missing from the headers above... +// just a small to let it compile fine in the OpenBeOS tree +#ifndef B_FILE_NAME_LENGTH +# define B_FILE_NAME_LENGTH 256 +#endif -// ToDo: implement the driver settings! It currently only exports the API -// so that drivers will find it +#include +#include +#include +#include +#include + + +#define SETTINGS_DIRECTORY "/boot/home/config/settings/kernel/drivers/" +#define SETTINGS_MAGIC 'DrvS' + +// Those maximum values are independent from the implementation - they +// have been chosen to make the code more robust against bad files +#define MAX_SETTINGS_SIZE 32768 +#define MAX_SETTINGS_LEVEL 8 + +#define CONTINUE_PARAMETER 1 + +struct settings_handle { + void *first_buffer; + int32 magic; + struct driver_settings settings; + char *text; +}; + +// Basic settings file access functions - since the driver settings API +// has to be able to load the settings files using the BIOS, before the +// kernel is running. +// ToDo: there is currently no way to change the settings ops - the +// kernel should just call a function at startup. +struct settings_ops { + int (*open)(const char *name); + void (*close)(int handle); + off_t (*filesize)(int handle); + size_t (*read)(int handle, char *buffer, size_t bufferSize); +}; + +// The BIOS calls are currently not implemented +// We need to have a stripped-down BFS driver for the bootloader; +// this one would implement the functionality we need here + +// ToDo: the code currently assumes a working malloc()/free() +// for the bootloader code. Most probably this is okay, though + +static int bios_open(const char *name); +static void bios_close(int handle); +static off_t bios_filesize(int handle); +static size_t bios_read(int handle, char *buffer, size_t bufferSize); + +static const struct settings_ops kBiosOps = { + bios_open, + bios_close, + bios_filesize, + bios_read, +}; + +// The kernel calls are used after the kernel has been loaded and +// initialized by the bootloader. + +static int kernel_open(const char *name); +static void kernel_close(int handle); +static off_t kernel_filesize(int handle); +static size_t kernel_read(int handle, char *buffer, size_t bufferSize); + +static const struct settings_ops kKernelOps = { + kernel_open, + kernel_close, + kernel_filesize, + kernel_read, +}; + +// ToDo: the BIOS API must be used at first when we have a real bootloader +static const struct settings_ops *gSettingsOps = &kKernelOps; + + +static int +bios_open(const char *name) +{ + // not yet implemented + // has to use a stripped down BFS driver to get down to the file + return B_ERROR; +} + + +static void +bios_close(int handle) +{ + // not yet implemented +} + + +static off_t +bios_filesize(int handle) +{ + // not yet implemented + return 0; +} + + +static size_t +bios_read(int handle, char *buffer, size_t bufferSize) +{ + // not yet implemented + return B_ERROR; +} + + +static int +kernel_open(const char *name) +{ + char path[B_FILE_NAME_LENGTH + 64]; + strcpy(path, SETTINGS_DIRECTORY); + strlcat(path, name, sizeof(path)); + + return open(path, O_RDONLY); +} + + +static void +kernel_close(int handle) +{ + close(handle); +} + + +static off_t +kernel_filesize(int handle) +{ + struct stat stat; + if (fstat(handle, &stat) < B_OK) + return B_ERROR; + + return stat.st_size; +} + + +static size_t +kernel_read(int handle, char *buffer, size_t bufferSize) +{ + return read(handle, buffer, bufferSize); +} + + +// #pragma mark - +// Functions not part of the public API + + +static inline bool +check_handle(struct settings_handle *handle) +{ + if (handle == NULL + || handle->magic != SETTINGS_MAGIC) + return false; + + return true; +} + + +static driver_parameter * +get_parameter(struct settings_handle *handle, const char *name) +{ + int32 i; + for (i = handle->settings.parameter_count; i-- > 0;) { + if (!strcmp(handle->settings.parameters[i].name, name)) + return &handle->settings.parameters[i]; + } + return NULL; +} + + +static status_t +get_word(char **_pos, char **_word, bool allowNewLine) +{ + char *pos = *_pos; + bool quoted = false, newLine = false, end = false; + bool escaped = false; + + // Skip any white space and comments + while (pos[0] && ((allowNewLine && (isspace(pos[0]) || pos[0] == '#')) + || (!allowNewLine && (pos[0] == '\t' || pos[0] == ' ')))) { + // skip any comment lines + if (pos[0] == '#') { + while (*pos && *pos != '\n') + pos++; + } + pos++; + } + + if (pos[0] == '}') + return B_NO_ERROR; + + // Read in a word - might contain escaped (\) spaces, or it + // might also be quoted ("). + + if (pos[0] == '"') { + quoted = true; + pos++; + } + *_word = pos; + + while (pos[0]) { + if (pos[0] == '\\' && pos[1] == ' ') { + escaped = true; + pos++; + } else if (quoted && pos[0] == '\\' && pos[1] == '"') { + escaped = true; + pos++; + } else if (pos[0] == '\n') + break; + else if ((!quoted && isspace(pos[0])) || (quoted && pos[0] == '"')) + break; + + pos++; + } + + // "String exceeds line" - missing end quote + if (quoted && pos[0] != '"') + return B_BAD_DATA; + + end = pos[0] == '\0'; + newLine = pos[0] == '\n' || end; + pos[0] = '\0'; + + // Correct name if there were any escaped characters + if (escaped) { + char *word = *_word; + while (word < pos) { + if (word[0] == '\\' + && ((quoted && word[1] == '"') + || (!quoted && word[1] == ' '))) + memmove(word, word + 1, pos - word); + + word++; + } + } + + if (end) { + *_pos = pos; + return B_OK; + } + + // Scan for next beginning word, open brackets, or comment start + + pos++; + while (true) { + *_pos = pos; + if (!pos[0]) + return B_NO_ERROR; + + if (pos[0] == '\n') { + // an open bracket '{' could follow after the first + // newline, but not later + if (newLine) + return B_NO_ERROR; + + newLine = true; + } else if (pos[0] == '{' || pos[0] == '}' || pos[0] == '#') + return B_NO_ERROR; + else if (!isspace(pos[0])) + return newLine ? B_NO_ERROR : CONTINUE_PARAMETER; + + pos++; + } +} + + +static status_t +parse_parameter(struct driver_parameter *parameter, char **_pos, int32 level) +{ + char *pos = *_pos; + status_t status; + + // initialize parameter first + memset(parameter, 0, sizeof(struct driver_parameter)); + + status = get_word(&pos, ¶meter->name, true); + if (status == CONTINUE_PARAMETER) { + while (status == CONTINUE_PARAMETER) { + char **newArray, *value; + status = get_word(&pos, &value, false); + if (status < B_OK) + break; + + // enlarge value array and save the value + + newArray = realloc(parameter->values, (parameter->value_count + 1) * sizeof(char *)); + if (newArray == NULL) + return B_NO_MEMORY; + + parameter->values = newArray; + parameter->values[parameter->value_count++] = value; + } + } + *_pos = pos; + return status; +} + + +static status_t +parse_parameters(struct driver_parameter **parameters, int *count, char **pos, int32 level) +{ + if (level > MAX_SETTINGS_LEVEL) + return B_LINK_LIMIT; + + while (true) { + struct driver_parameter *newArray, *parameter; + status_t status; + newArray = realloc(*parameters, (*count + 1) * sizeof(struct driver_parameter)); + if (newArray == NULL) + return B_NO_MEMORY; + + parameter = &newArray[*count]; + + status = parse_parameter(parameter, pos, level); + if (status < B_OK) + return status; + + *parameters = newArray; + (*count)++; + + // check for level beginning and end + if (**pos == '{') { + // if we go a level deeper, just start all over again... + (*pos)++; + status = parse_parameters(¶meter->parameters, ¶meter->parameter_count, pos, level + 1); + if (status < B_OK) + return status; + } + if ((**pos == '}' && level > 0) + || (**pos == '\0' && level == 0)) { + // take the closing bracket from the stack + (*pos)++; + return B_OK; + } + + // obviously, something has gone wrong + if (**pos == '}' || **pos == '\0') + return B_ERROR; + } +} + + +static status_t +parse_settings(struct settings_handle *handle) +{ + char *text = handle->text; + + memset(&handle->settings, 0, sizeof(struct driver_settings)); + return parse_parameters(&handle->settings.parameters, &handle->settings.parameter_count, &text, 0); +} + + +static void +free_parameter(struct driver_parameter *parameter) +{ + int32 i; + for (i = parameter->parameter_count; i-- > 0;) + free_parameter(¶meter->parameters[i]); + + free(parameter->parameters); + free(parameter->values); +} + + +static void +free_settings(struct settings_handle *handle) +{ + int32 i; + for (i = handle->settings.parameter_count; i-- > 0;) + free_parameter(&handle->settings.parameters[i]); + + free(handle->settings.parameters); + + free(handle->text); + free(handle); +} + + +// ToDo: the API to add an item to the driver_settings is obviously accessable +// to the kernel, so we should provide it, too (in BeOS this is used to add +// driver settings at boot time, using the safe boot menu). + +//static status_t +//add_driver_parameter(const char *name, ) +//{ +//} + + +// ToDo: make this function available to the kernel + +//static void +//driver_settings_kernel_init(void) +//{ +// // switch the disk access functions to use those from the +// // kernel rather than those from the BIOS +// gSettingsOps = &kKernelOps; +//} + + +// #pragma mark - +// The public API implementation status_t unload_driver_settings(void *handle) { - return B_BAD_VALUE; + if (!check_handle(handle)) + return B_BAD_VALUE; + + free_settings(handle); + return B_OK; } void * load_driver_settings(const char *driverName) { + off_t size; + int file; + + if (driverName == NULL) + return NULL; + + file = gSettingsOps->open(driverName); + if (file < B_OK) + return NULL; + + // Allocate a buffer and read the whole file into it. + // We will keep this buffer in memory, until the settings + // are unloaded. + // The driver_parameter::name field will point directly + // to this buffer. + + size = gSettingsOps->filesize(file); + if (size > B_OK && size < MAX_SETTINGS_SIZE) { + char *text = (char *)malloc(size + 1); + if (text != NULL && gSettingsOps->read(file, text, size) == size) { + struct settings_handle *handle = malloc(sizeof(struct settings_handle)); + if (handle != NULL) { + text[size] = '\0'; + + handle->magic = SETTINGS_MAGIC; + handle->text = text; + + if (parse_settings(handle) == B_OK) { + gSettingsOps->close(file); + return handle; + } + + free(handle); + } + } + // "text" might be NULL here, but that's allowed + free(text); + } + + gSettingsOps->close(file); return NULL; } @@ -29,6 +477,41 @@ load_driver_settings(const char *driverName) bool get_driver_boolean_parameter(void *handle, const char *keyName, bool unknownValue, bool noArgValue) { + driver_parameter *parameter; + char *boolean; + + if (!check_handle(handle)) + return unknownValue; + + // check for the parameter + if ((parameter = get_parameter(handle, keyName)) == NULL) + return unknownValue; + + // ToDo: This takes just the first argument/value, and checks that one; + // I don't know if they are used to work that way in BeOS, though. + + // check for the argument + if (parameter->value_count <= 0) + return noArgValue; + + boolean = parameter->values[0]; + if (!strcmp(boolean, "1") + || !strcasecmp(boolean, "true") + || !strcasecmp(boolean, "yes") + || !strcasecmp(boolean, "on") + || !strcasecmp(boolean, "enable") + || !strcasecmp(boolean, "enabled")) + return true; + + if (!strcmp(boolean, "0") + || !strcasecmp(boolean, "false") + || !strcasecmp(boolean, "no") + || !strcasecmp(boolean, "off") + || !strcasecmp(boolean, "disable") + || !strcasecmp(boolean, "disabled")) + return false; + + // if no known keyword is found, "unknownValue" is returned return unknownValue; } @@ -36,13 +519,29 @@ get_driver_boolean_parameter(void *handle, const char *keyName, bool unknownValu const char * get_driver_parameter(void *handle, const char *keyName, const char *unknownValue, const char *noArgValue) { - return unknownValue; + struct driver_parameter *parameter; + + if (!check_handle(handle)) + return unknownValue; + + // check for the parameter + if ((parameter = get_parameter(handle, keyName)) == NULL) + return unknownValue; + + // check for the argument + if (parameter->value_count <= 0) + return noArgValue; + + return parameter->values[0]; } const driver_settings * get_driver_settings(void *handle) { - return NULL; + if (!check_handle(handle)) + return NULL; + + return &((struct settings_handle *)handle)->settings; }