summaryrefslogtreecommitdiff
path: root/drivers/staging/unisys/visorchipset/parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/unisys/visorchipset/parser.c')
-rw-r--r--drivers/staging/unisys/visorchipset/parser.c474
1 files changed, 474 insertions, 0 deletions
diff --git a/drivers/staging/unisys/visorchipset/parser.c b/drivers/staging/unisys/visorchipset/parser.c
new file mode 100644
index 000000000000..b408d415bd8c
--- /dev/null
+++ b/drivers/staging/unisys/visorchipset/parser.c
@@ -0,0 +1,474 @@
+/* parser.c
+ *
+ * Copyright © 2010 - 2013 UNISYS CORPORATION
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ */
+
+#include "parser.h"
+#include "memregion.h"
+#include "controlvmchannel.h"
+#include <linux/ctype.h>
+#include <linux/mm.h>
+
+#define MYDRVNAME "visorchipset_parser"
+#define CURRENT_FILE_PC VISOR_CHIPSET_PC_parser_c
+
+/* We will refuse to allocate more than this many bytes to copy data from
+ * incoming payloads. This serves as a throttling mechanism.
+ */
+#define MAX_CONTROLVM_PAYLOAD_BYTES (1024*128)
+static ulong Controlvm_Payload_Bytes_Buffered;
+
+struct PARSER_CONTEXT_Tag {
+ ulong allocbytes;
+ ulong param_bytes;
+ u8 *curr;
+ ulong bytes_remaining;
+ BOOL byte_stream;
+ char data[0];
+};
+
+static PARSER_CONTEXT *
+parser_init_guts(U64 addr, U32 bytes, BOOL isLocal,
+ BOOL hasStandardPayloadHeader, BOOL *tryAgain)
+{
+ int allocbytes = sizeof(PARSER_CONTEXT) + bytes;
+ PARSER_CONTEXT *rc = NULL;
+ PARSER_CONTEXT *ctx = NULL;
+ MEMREGION *rgn = NULL;
+ ULTRA_CONTROLVM_PARAMETERS_HEADER *phdr = NULL;
+
+ if (tryAgain)
+ *tryAgain = FALSE;
+ if (!hasStandardPayloadHeader)
+ /* alloc and 0 extra byte to ensure payload is
+ * '\0'-terminated
+ */
+ allocbytes++;
+ if ((Controlvm_Payload_Bytes_Buffered + bytes)
+ > MAX_CONTROLVM_PAYLOAD_BYTES) {
+ ERRDRV("%s (%s:%d) - prevented allocation of %d bytes to prevent exceeding throttling max (%d)",
+ __func__, __FILE__, __LINE__, allocbytes,
+ MAX_CONTROLVM_PAYLOAD_BYTES);
+ if (tryAgain)
+ *tryAgain = TRUE;
+ rc = NULL;
+ goto Away;
+ }
+ ctx = kzalloc(allocbytes, GFP_KERNEL|__GFP_NORETRY);
+ if (ctx == NULL) {
+ ERRDRV("%s (%s:%d) - failed to allocate %d bytes",
+ __func__, __FILE__, __LINE__, allocbytes);
+ if (tryAgain)
+ *tryAgain = TRUE;
+ rc = NULL;
+ goto Away;
+ }
+
+ ctx->allocbytes = allocbytes;
+ ctx->param_bytes = bytes;
+ ctx->curr = NULL;
+ ctx->bytes_remaining = 0;
+ ctx->byte_stream = FALSE;
+ if (isLocal) {
+ void *p;
+ if (addr > virt_to_phys(high_memory - 1)) {
+ ERRDRV("%s - bad local address (0x%-16.16Lx for %lu)",
+ __func__,
+ (unsigned long long) addr, (ulong) bytes);
+ rc = NULL;
+ goto Away;
+ }
+ p = __va((ulong) (addr));
+ memcpy(ctx->data, p, bytes);
+ } else {
+ rgn = visor_memregion_create(addr, bytes);
+ if (!rgn) {
+ rc = NULL;
+ goto Away;
+ }
+ if (visor_memregion_read(rgn, 0, ctx->data, bytes) < 0) {
+ rc = NULL;
+ goto Away;
+ }
+ }
+ if (!hasStandardPayloadHeader) {
+ ctx->byte_stream = TRUE;
+ rc = ctx;
+ goto Away;
+ }
+ phdr = (ULTRA_CONTROLVM_PARAMETERS_HEADER *) (ctx->data);
+ if (phdr->TotalLength != bytes) {
+ ERRDRV("%s - bad total length %lu (should be %lu)",
+ __func__,
+ (ulong) (phdr->TotalLength), (ulong) (bytes));
+ rc = NULL;
+ goto Away;
+ }
+ if (phdr->TotalLength < phdr->HeaderLength) {
+ ERRDRV("%s - total length < header length (%lu < %lu)",
+ __func__,
+ (ulong) (phdr->TotalLength),
+ (ulong) (phdr->HeaderLength));
+ rc = NULL;
+ goto Away;
+ }
+ if (phdr->HeaderLength < sizeof(ULTRA_CONTROLVM_PARAMETERS_HEADER)) {
+ ERRDRV("%s - header is too small (%lu < %lu)",
+ __func__,
+ (ulong) (phdr->HeaderLength),
+ (ulong) (sizeof(ULTRA_CONTROLVM_PARAMETERS_HEADER)));
+ rc = NULL;
+ goto Away;
+ }
+
+ rc = ctx;
+Away:
+ if (rgn) {
+ visor_memregion_destroy(rgn);
+ rgn = NULL;
+ }
+ if (rc)
+ Controlvm_Payload_Bytes_Buffered += ctx->param_bytes;
+ else {
+ if (ctx) {
+ parser_done(ctx);
+ ctx = NULL;
+ }
+ }
+ return rc;
+}
+
+PARSER_CONTEXT *
+parser_init(U64 addr, U32 bytes, BOOL isLocal, BOOL *tryAgain)
+{
+ return parser_init_guts(addr, bytes, isLocal, TRUE, tryAgain);
+}
+
+/* Call this instead of parser_init() if the payload area consists of just
+ * a sequence of bytes, rather than a ULTRA_CONTROLVM_PARAMETERS_HEADER
+ * structures. Afterwards, you can call parser_simpleString_get() or
+ * parser_byteStream_get() to obtain the data.
+ */
+PARSER_CONTEXT *
+parser_init_byteStream(U64 addr, U32 bytes, BOOL isLocal, BOOL *tryAgain)
+{
+ return parser_init_guts(addr, bytes, isLocal, FALSE, tryAgain);
+}
+
+/* Obtain '\0'-terminated copy of string in payload area.
+ */
+char *
+parser_simpleString_get(PARSER_CONTEXT *ctx)
+{
+ if (!ctx->byte_stream)
+ return NULL;
+ return ctx->data; /* note this IS '\0'-terminated, because of
+ * the num of bytes we alloc+clear in
+ * parser_init_byteStream() */
+}
+
+/* Obtain a copy of the buffer in the payload area.
+ */
+void *
+parser_byteStream_get(PARSER_CONTEXT *ctx, ulong *nbytes)
+{
+ if (!ctx->byte_stream)
+ return NULL;
+ if (nbytes)
+ *nbytes = ctx->param_bytes;
+ return (void *) ctx->data;
+}
+
+GUID
+parser_id_get(PARSER_CONTEXT *ctx)
+{
+ ULTRA_CONTROLVM_PARAMETERS_HEADER *phdr = NULL;
+
+ if (ctx == NULL) {
+ ERRDRV("%s (%s:%d) - no context",
+ __func__, __FILE__, __LINE__);
+ return Guid0;
+ }
+ phdr = (ULTRA_CONTROLVM_PARAMETERS_HEADER *) (ctx->data);
+ return phdr->Id;
+}
+
+void
+parser_param_start(PARSER_CONTEXT *ctx, PARSER_WHICH_STRING which_string)
+{
+ ULTRA_CONTROLVM_PARAMETERS_HEADER *phdr = NULL;
+
+ if (ctx == NULL) {
+ ERRDRV("%s (%s:%d) - no context",
+ __func__, __FILE__, __LINE__);
+ goto Away;
+ }
+ phdr = (ULTRA_CONTROLVM_PARAMETERS_HEADER *) (ctx->data);
+ switch (which_string) {
+ case PARSERSTRING_INITIATOR:
+ ctx->curr = ctx->data + phdr->InitiatorOffset;
+ ctx->bytes_remaining = phdr->InitiatorLength;
+ break;
+ case PARSERSTRING_TARGET:
+ ctx->curr = ctx->data + phdr->TargetOffset;
+ ctx->bytes_remaining = phdr->TargetLength;
+ break;
+ case PARSERSTRING_CONNECTION:
+ ctx->curr = ctx->data + phdr->ConnectionOffset;
+ ctx->bytes_remaining = phdr->ConnectionLength;
+ break;
+ case PARSERSTRING_NAME:
+ ctx->curr = ctx->data + phdr->NameOffset;
+ ctx->bytes_remaining = phdr->NameLength;
+ break;
+ default:
+ ERRDRV("%s - bad which_string %d", __func__, which_string);
+ break;
+ }
+
+Away:
+ return;
+}
+
+void
+parser_done(PARSER_CONTEXT *ctx)
+{
+ if (!ctx)
+ return;
+ Controlvm_Payload_Bytes_Buffered -= ctx->param_bytes;
+ kfree(ctx);
+}
+
+/** Return length of string not counting trailing spaces. */
+static int
+string_length_no_trail(char *s, int len)
+{
+ int i = len - 1;
+ while (i >= 0) {
+ if (!isspace(s[i]))
+ return i + 1;
+ i--;
+ }
+ return 0;
+}
+
+/** Grab the next name and value out of the parameter buffer.
+ * The entire parameter buffer looks like this:
+ * <name>=<value>\0
+ * <name>=<value>\0
+ * ...
+ * \0
+ * If successful, the next <name> value is returned within the supplied
+ * <nam> buffer (the value is always upper-cased), and the corresponding
+ * <value> is returned within a kmalloc()ed buffer, whose pointer is
+ * provided as the return value of this function.
+ * (The total number of bytes allocated is strlen(<value>)+1.)
+ *
+ * NULL is returned to indicate failure, which can occur for several reasons:
+ * - all <name>=<value> pairs have already been processed
+ * - bad parameter
+ * - parameter buffer ends prematurely (couldn't find an '=' or '\0' within
+ * the confines of the parameter buffer)
+ * - the <nam> buffer is not large enough to hold the <name> of the next
+ * parameter
+ */
+void *
+parser_param_get(PARSER_CONTEXT *ctx, char *nam, int namesize)
+{
+ u8 *pscan, *pnam = nam;
+ ulong nscan;
+ int value_length = -1, orig_value_length = -1;
+ void *value = NULL;
+ int i;
+ int closing_quote = 0;
+
+ if (!ctx)
+ return NULL;
+ pscan = ctx->curr;
+ nscan = ctx->bytes_remaining;
+ if (nscan == 0)
+ return NULL;
+ if (*pscan == '\0')
+ /* This is the normal return point after you have processed
+ * all of the <name>=<value> pairs in a syntactically-valid
+ * parameter buffer.
+ */
+ return NULL;
+
+ /* skip whitespace */
+ while (isspace(*pscan)) {
+ pscan++;
+ nscan--;
+ if (nscan == 0)
+ return NULL;
+ }
+
+ while (*pscan != ':') {
+ if (namesize <= 0) {
+ ERRDRV("%s - name too big", __func__);
+ return NULL;
+ }
+ *pnam = toupper(*pscan);
+ pnam++;
+ namesize--;
+ pscan++;
+ nscan--;
+ if (nscan == 0) {
+ ERRDRV("%s - unexpected end of input parsing name",
+ __func__);
+ return NULL;
+ }
+ }
+ if (namesize <= 0) {
+ ERRDRV("%s - name too big", __func__);
+ return NULL;
+ }
+ *pnam = '\0';
+ nam[string_length_no_trail(nam, strlen(nam))] = '\0';
+
+ /* point to char immediately after ":" in "<name>:<value>" */
+ pscan++;
+ nscan--;
+ /* skip whitespace */
+ while (isspace(*pscan)) {
+ pscan++;
+ nscan--;
+ if (nscan == 0) {
+ ERRDRV("%s - unexpected end of input looking for value",
+ __func__);
+ return NULL;
+ }
+ }
+ if (nscan == 0) {
+ ERRDRV("%s - unexpected end of input looking for value",
+ __func__);
+ return NULL;
+ }
+ if (*pscan == '\'' || *pscan == '"') {
+ closing_quote = *pscan;
+ pscan++;
+ nscan--;
+ if (nscan == 0) {
+ ERRDRV("%s - unexpected end of input after %c",
+ __func__, closing_quote);
+ return NULL;
+ }
+ }
+
+ /* look for a separator character, terminator character, or
+ * end of data
+ */
+ for (i = 0, value_length = -1; i < nscan; i++) {
+ if (closing_quote) {
+ if (pscan[i] == '\0') {
+ ERRDRV("%s - unexpected end of input parsing quoted value", __func__);
+ return NULL;
+ }
+ if (pscan[i] == closing_quote) {
+ value_length = i;
+ break;
+ }
+ } else
+ if (pscan[i] == ',' || pscan[i] == ';'
+ || pscan[i] == '\0') {
+ value_length = i;
+ break;
+ }
+ }
+ if (value_length < 0) {
+ if (closing_quote) {
+ ERRDRV("%s - unexpected end of input parsing quoted value", __func__);
+ return NULL;
+ }
+ value_length = nscan;
+ }
+ orig_value_length = value_length;
+ if (closing_quote == 0)
+ value_length = string_length_no_trail(pscan, orig_value_length);
+ value = kmalloc(value_length + 1, GFP_KERNEL|__GFP_NORETRY);
+ if (value == NULL)
+ return NULL;
+ memcpy(value, pscan, value_length);
+ ((u8 *) (value))[value_length] = '\0';
+
+ pscan += orig_value_length;
+ nscan -= orig_value_length;
+
+ /* skip past separator or closing quote */
+ if (nscan > 0) {
+ if (*pscan != '\0') {
+ pscan++;
+ nscan--;
+ }
+ }
+
+ if (closing_quote && (nscan > 0)) {
+ /* we still need to skip around the real separator if present */
+ /* first, skip whitespace */
+ while (isspace(*pscan)) {
+ pscan++;
+ nscan--;
+ if (nscan == 0)
+ break;
+ }
+ if (nscan > 0) {
+ if (*pscan == ',' || *pscan == ';') {
+ pscan++;
+ nscan--;
+ } else if (*pscan != '\0') {
+ ERRDRV("%s - missing separator after quoted string", __func__);
+ kfree(value);
+ value = NULL;
+ return NULL;
+ }
+ }
+ }
+ ctx->curr = pscan;
+ ctx->bytes_remaining = nscan;
+ return value;
+}
+
+void *
+parser_string_get(PARSER_CONTEXT *ctx)
+{
+ u8 *pscan;
+ ulong nscan;
+ int value_length = -1;
+ void *value = NULL;
+ int i;
+
+ if (!ctx)
+ return NULL;
+ pscan = ctx->curr;
+ nscan = ctx->bytes_remaining;
+ if (nscan == 0)
+ return NULL;
+ if (!pscan)
+ return NULL;
+ for (i = 0, value_length = -1; i < nscan; i++)
+ if (pscan[i] == '\0') {
+ value_length = i;
+ break;
+ }
+ if (value_length < 0) /* '\0' was not included in the length */
+ value_length = nscan;
+ value = kmalloc(value_length + 1, GFP_KERNEL|__GFP_NORETRY);
+ if (value == NULL)
+ return NULL;
+ if (value_length > 0)
+ memcpy(value, pscan, value_length);
+ ((u8 *) (value))[value_length] = '\0';
+ return value;
+}