Merge remote-tracking branch 'efi/urgent' into x86/urgent
authorH. Peter Anvin <hpa@linux.intel.com>
Sat, 20 Apr 2013 00:09:03 +0000 (17:09 -0700)
committerH. Peter Anvin <hpa@linux.intel.com>
Sat, 20 Apr 2013 00:09:03 +0000 (17:09 -0700)
Matt Fleming (1):
      x86, efivars: firmware bug workarounds should be in platform
      code

Matthew Garrett (3):
      Move utf16 functions to kernel core and rename
      efi: Pass boot services variable info to runtime code
      efi: Distinguish between "remaining space" and actually used
      space

Richard Weinberger (2):
      x86,efi: Check max_size only if it is non-zero.
      x86,efi: Implement efi_no_storage_paranoia parameter

Sergey Vlasov (2):
      x86/Kconfig: Make EFI select UCS2_STRING
      efi: Export efi_query_variable_store() for efivars.ko

Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
13 files changed:
Documentation/kernel-parameters.txt
arch/x86/Kconfig
arch/x86/boot/compressed/eboot.c
arch/x86/include/asm/efi.h
arch/x86/include/uapi/asm/bootparam.h
arch/x86/platform/efi/efi.c
drivers/firmware/Kconfig
drivers/firmware/efivars.c
include/linux/efi.h
include/linux/ucs2_string.h [new file with mode: 0644]
lib/Kconfig
lib/Makefile
lib/ucs2_string.c [new file with mode: 0644]

index 4609e81..d1cc3a9 100644 (file)
@@ -788,6 +788,12 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
        edd=            [EDD]
                        Format: {"off" | "on" | "skip[mbr]"}
 
+       efi_no_storage_paranoia [EFI; X86]
+                       Using this parameter you can use more than 50% of
+                       your efi variable storage. Use this parameter only if
+                       you are really sure that your UEFI does sane gc and
+                       fulfills the spec otherwise your board may brick.
+
        eisa_irq_edge=  [PARISC,HW]
                        See header of drivers/parisc/eisa.c.
 
index 70c0f3d..15b5cef 100644 (file)
@@ -1549,6 +1549,7 @@ config X86_SMAP
 config EFI
        bool "EFI runtime service support"
        depends on ACPI
+       select UCS2_STRING
        ---help---
          This enables the kernel to use EFI runtime services that are
          available (such as the EFI variable services).
index c205035..8615f75 100644 (file)
@@ -251,6 +251,51 @@ static void find_bits(unsigned long mask, u8 *pos, u8 *size)
        *size = len;
 }
 
+static efi_status_t setup_efi_vars(struct boot_params *params)
+{
+       struct setup_data *data;
+       struct efi_var_bootdata *efidata;
+       u64 store_size, remaining_size, var_size;
+       efi_status_t status;
+
+       if (!sys_table->runtime->query_variable_info)
+               return EFI_UNSUPPORTED;
+
+       data = (struct setup_data *)(unsigned long)params->hdr.setup_data;
+
+       while (data && data->next)
+               data = (struct setup_data *)(unsigned long)data->next;
+
+       status = efi_call_phys4(sys_table->runtime->query_variable_info,
+                               EFI_VARIABLE_NON_VOLATILE |
+                               EFI_VARIABLE_BOOTSERVICE_ACCESS |
+                               EFI_VARIABLE_RUNTIME_ACCESS, &store_size,
+                               &remaining_size, &var_size);
+
+       if (status != EFI_SUCCESS)
+               return status;
+
+       status = efi_call_phys3(sys_table->boottime->allocate_pool,
+                               EFI_LOADER_DATA, sizeof(*efidata), &efidata);
+
+       if (status != EFI_SUCCESS)
+               return status;
+
+       efidata->data.type = SETUP_EFI_VARS;
+       efidata->data.len = sizeof(struct efi_var_bootdata) -
+               sizeof(struct setup_data);
+       efidata->data.next = 0;
+       efidata->store_size = store_size;
+       efidata->remaining_size = remaining_size;
+       efidata->max_var_size = var_size;
+
+       if (data)
+               data->next = (unsigned long)efidata;
+       else
+               params->hdr.setup_data = (unsigned long)efidata;
+
+}
+
 static efi_status_t setup_efi_pci(struct boot_params *params)
 {
        efi_pci_io_protocol *pci;
@@ -1157,6 +1202,8 @@ struct boot_params *efi_main(void *handle, efi_system_table_t *_table,
 
        setup_graphics(boot_params);
 
+       setup_efi_vars(boot_params);
+
        setup_efi_pci(boot_params);
 
        status = efi_call_phys3(sys_table->boottime->allocate_pool,
index 60c89f3..2fb5d58 100644 (file)
@@ -102,6 +102,13 @@ extern void efi_call_phys_epilog(void);
 extern void efi_unmap_memmap(void);
 extern void efi_memory_uc(u64 addr, unsigned long size);
 
+struct efi_var_bootdata {
+       struct setup_data data;
+       u64 store_size;
+       u64 remaining_size;
+       u64 max_var_size;
+};
+
 #ifdef CONFIG_EFI
 
 static inline bool efi_is_native(void)
index c15ddaf..0874424 100644 (file)
@@ -6,6 +6,7 @@
 #define SETUP_E820_EXT                 1
 #define SETUP_DTB                      2
 #define SETUP_PCI                      3
+#define SETUP_EFI_VARS                 4
 
 /* ram_size flags */
 #define RAMDISK_IMAGE_START_MASK       0x07FF
index 5f2ecaf..e4a86a6 100644 (file)
@@ -41,6 +41,7 @@
 #include <linux/io.h>
 #include <linux/reboot.h>
 #include <linux/bcd.h>
+#include <linux/ucs2_string.h>
 
 #include <asm/setup.h>
 #include <asm/efi.h>
 
 #define EFI_DEBUG      1
 
+/*
+ * There's some additional metadata associated with each
+ * variable. Intel's reference implementation is 60 bytes - bump that
+ * to account for potential alignment constraints
+ */
+#define VAR_METADATA_SIZE 64
+
 struct efi __read_mostly efi = {
        .mps        = EFI_INVALID_TABLE_ADDR,
        .acpi       = EFI_INVALID_TABLE_ADDR,
@@ -69,6 +77,13 @@ struct efi_memory_map memmap;
 static struct efi efi_phys __initdata;
 static efi_system_table_t efi_systab __initdata;
 
+static u64 efi_var_store_size;
+static u64 efi_var_remaining_size;
+static u64 efi_var_max_var_size;
+static u64 boot_used_size;
+static u64 boot_var_size;
+static u64 active_size;
+
 unsigned long x86_efi_facility;
 
 /*
@@ -98,6 +113,15 @@ static int __init setup_add_efi_memmap(char *arg)
 }
 early_param("add_efi_memmap", setup_add_efi_memmap);
 
+static bool efi_no_storage_paranoia;
+
+static int __init setup_storage_paranoia(char *arg)
+{
+       efi_no_storage_paranoia = true;
+       return 0;
+}
+early_param("efi_no_storage_paranoia", setup_storage_paranoia);
+
 
 static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
 {
@@ -162,8 +186,53 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
                                               efi_char16_t *name,
                                               efi_guid_t *vendor)
 {
-       return efi_call_virt3(get_next_variable,
-                             name_size, name, vendor);
+       efi_status_t status;
+       static bool finished = false;
+       static u64 var_size;
+
+       status = efi_call_virt3(get_next_variable,
+                               name_size, name, vendor);
+
+       if (status == EFI_NOT_FOUND) {
+               finished = true;
+               if (var_size < boot_used_size) {
+                       boot_var_size = boot_used_size - var_size;
+                       active_size += boot_var_size;
+               } else {
+                       printk(KERN_WARNING FW_BUG  "efi: Inconsistent initial sizes\n");
+               }
+       }
+
+       if (boot_used_size && !finished) {
+               unsigned long size;
+               u32 attr;
+               efi_status_t s;
+               void *tmp;
+
+               s = virt_efi_get_variable(name, vendor, &attr, &size, NULL);
+
+               if (s != EFI_BUFFER_TOO_SMALL || !size)
+                       return status;
+
+               tmp = kmalloc(size, GFP_ATOMIC);
+
+               if (!tmp)
+                       return status;
+
+               s = virt_efi_get_variable(name, vendor, &attr, &size, tmp);
+
+               if (s == EFI_SUCCESS && (attr & EFI_VARIABLE_NON_VOLATILE)) {
+                       var_size += size;
+                       var_size += ucs2_strsize(name, 1024);
+                       active_size += size;
+                       active_size += VAR_METADATA_SIZE;
+                       active_size += ucs2_strsize(name, 1024);
+               }
+
+               kfree(tmp);
+       }
+
+       return status;
 }
 
 static efi_status_t virt_efi_set_variable(efi_char16_t *name,
@@ -172,9 +241,34 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
                                          unsigned long data_size,
                                          void *data)
 {
-       return efi_call_virt5(set_variable,
-                             name, vendor, attr,
-                             data_size, data);
+       efi_status_t status;
+       u32 orig_attr = 0;
+       unsigned long orig_size = 0;
+
+       status = virt_efi_get_variable(name, vendor, &orig_attr, &orig_size,
+                                      NULL);
+
+       if (status != EFI_BUFFER_TOO_SMALL)
+               orig_size = 0;
+
+       status = efi_call_virt5(set_variable,
+                               name, vendor, attr,
+                               data_size, data);
+
+       if (status == EFI_SUCCESS) {
+               if (orig_size) {
+                       active_size -= orig_size;
+                       active_size -= ucs2_strsize(name, 1024);
+                       active_size -= VAR_METADATA_SIZE;
+               }
+               if (data_size) {
+                       active_size += data_size;
+                       active_size += ucs2_strsize(name, 1024);
+                       active_size += VAR_METADATA_SIZE;
+               }
+       }
+
+       return status;
 }
 
 static efi_status_t virt_efi_query_variable_info(u32 attr,
@@ -682,6 +776,9 @@ void __init efi_init(void)
        char vendor[100] = "unknown";
        int i = 0;
        void *tmp;
+       struct setup_data *data;
+       struct efi_var_bootdata *efi_var_data;
+       u64 pa_data;
 
 #ifdef CONFIG_X86_32
        if (boot_params.efi_info.efi_systab_hi ||
@@ -699,6 +796,22 @@ void __init efi_init(void)
        if (efi_systab_init(efi_phys.systab))
                return;
 
+       pa_data = boot_params.hdr.setup_data;
+       while (pa_data) {
+               data = early_ioremap(pa_data, sizeof(*efi_var_data));
+               if (data->type == SETUP_EFI_VARS) {
+                       efi_var_data = (struct efi_var_bootdata *)data;
+
+                       efi_var_store_size = efi_var_data->store_size;
+                       efi_var_remaining_size = efi_var_data->remaining_size;
+                       efi_var_max_var_size = efi_var_data->max_var_size;
+               }
+               pa_data = data->next;
+               early_iounmap(data, sizeof(*efi_var_data));
+       }
+
+       boot_used_size = efi_var_store_size - efi_var_remaining_size;
+
        set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);
 
        /*
@@ -999,3 +1112,48 @@ u64 efi_mem_attributes(unsigned long phys_addr)
        }
        return 0;
 }
+
+/*
+ * Some firmware has serious problems when using more than 50% of the EFI
+ * variable store, i.e. it triggers bugs that can brick machines. Ensure that
+ * we never use more than this safe limit.
+ *
+ * Return EFI_SUCCESS if it is safe to write 'size' bytes to the variable
+ * store.
+ */
+efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
+{
+       efi_status_t status;
+       u64 storage_size, remaining_size, max_size;
+
+       status = efi.query_variable_info(attributes, &storage_size,
+                                        &remaining_size, &max_size);
+       if (status != EFI_SUCCESS)
+               return status;
+
+       if (!max_size && remaining_size > size)
+               printk_once(KERN_ERR FW_BUG "Broken EFI implementation"
+                           " is returning MaxVariableSize=0\n");
+       /*
+        * Some firmware implementations refuse to boot if there's insufficient
+        * space in the variable store. We account for that by refusing the
+        * write if permitting it would reduce the available space to under
+        * 50%. However, some firmware won't reclaim variable space until
+        * after the used (not merely the actively used) space drops below
+        * a threshold. We can approximate that case with the value calculated
+        * above. If both the firmware and our calculations indicate that the
+        * available space would drop below 50%, refuse the write.
+        */
+
+       if (!storage_size || size > remaining_size ||
+           (max_size && size > max_size))
+               return EFI_OUT_OF_RESOURCES;
+
+       if (!efi_no_storage_paranoia &&
+           ((active_size + size + VAR_METADATA_SIZE > storage_size / 2) &&
+            (remaining_size - size < storage_size / 2)))
+               return EFI_OUT_OF_RESOURCES;
+
+       return EFI_SUCCESS;
+}
+EXPORT_SYMBOL_GPL(efi_query_variable_store);
index 42c759a..3e53200 100644 (file)
@@ -39,6 +39,7 @@ config FIRMWARE_MEMMAP
 config EFI_VARS
        tristate "EFI Variable Support via sysfs"
        depends on EFI
+       select UCS2_STRING
        default n
        help
          If you say Y here, you are able to get EFI (Extensible Firmware
index 7acafb8..182ce94 100644 (file)
@@ -80,6 +80,7 @@
 #include <linux/slab.h>
 #include <linux/pstore.h>
 #include <linux/ctype.h>
+#include <linux/ucs2_string.h>
 
 #include <linux/fs.h>
 #include <linux/ramfs.h>
@@ -172,51 +173,6 @@ static void efivar_update_sysfs_entries(struct work_struct *);
 static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries);
 static bool efivar_wq_enabled = true;
 
-/* Return the number of unicode characters in data */
-static unsigned long
-utf16_strnlen(efi_char16_t *s, size_t maxlength)
-{
-       unsigned long length = 0;
-
-       while (*s++ != 0 && length < maxlength)
-               length++;
-       return length;
-}
-
-static inline unsigned long
-utf16_strlen(efi_char16_t *s)
-{
-       return utf16_strnlen(s, ~0UL);
-}
-
-/*
- * Return the number of bytes is the length of this string
- * Note: this is NOT the same as the number of unicode characters
- */
-static inline unsigned long
-utf16_strsize(efi_char16_t *data, unsigned long maxlength)
-{
-       return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t);
-}
-
-static inline int
-utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len)
-{
-       while (1) {
-               if (len == 0)
-                       return 0;
-               if (*a < *b)
-                       return -1;
-               if (*a > *b)
-                       return 1;
-               if (*a == 0) /* implies *b == 0 */
-                       return 0;
-               a++;
-               b++;
-               len--;
-       }
-}
-
 static bool
 validate_device_path(struct efi_variable *var, int match, u8 *buffer,
                     unsigned long len)
@@ -268,7 +224,7 @@ validate_load_option(struct efi_variable *var, int match, u8 *buffer,
        u16 filepathlength;
        int i, desclength = 0, namelen;
 
-       namelen = utf16_strnlen(var->VariableName, sizeof(var->VariableName));
+       namelen = ucs2_strnlen(var->VariableName, sizeof(var->VariableName));
 
        /* Either "Boot" or "Driver" followed by four digits of hex */
        for (i = match; i < match+4; i++) {
@@ -291,7 +247,7 @@ validate_load_option(struct efi_variable *var, int match, u8 *buffer,
         * There's no stored length for the description, so it has to be
         * found by hand
         */
-       desclength = utf16_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
+       desclength = ucs2_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
 
        /* Each boot entry must have a descriptor */
        if (!desclength)
@@ -436,24 +392,12 @@ static efi_status_t
 check_var_size_locked(struct efivars *efivars, u32 attributes,
                        unsigned long size)
 {
-       u64 storage_size, remaining_size, max_size;
-       efi_status_t status;
        const struct efivar_operations *fops = efivars->ops;
 
-       if (!efivars->ops->query_variable_info)
+       if (!efivars->ops->query_variable_store)
                return EFI_UNSUPPORTED;
 
-       status = fops->query_variable_info(attributes, &storage_size,
-                                          &remaining_size, &max_size);
-
-       if (status != EFI_SUCCESS)
-               return status;
-
-       if (!storage_size || size > remaining_size || size > max_size ||
-           (remaining_size - size) < (storage_size / 2))
-               return EFI_OUT_OF_RESOURCES;
-
-       return status;
+       return fops->query_variable_store(attributes, size);
 }
 
 
@@ -593,7 +537,7 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
        spin_lock_irq(&efivars->lock);
 
        status = check_var_size_locked(efivars, new_var->Attributes,
-              new_var->DataSize + utf16_strsize(new_var->VariableName, 1024));
+              new_var->DataSize + ucs2_strsize(new_var->VariableName, 1024));
 
        if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED)
                status = efivars->ops->set_variable(new_var->VariableName,
@@ -771,7 +715,7 @@ static ssize_t efivarfs_file_write(struct file *file,
         * QueryVariableInfo() isn't supported by the firmware.
         */
 
-       varsize = datasize + utf16_strsize(var->var.VariableName, 1024);
+       varsize = datasize + ucs2_strsize(var->var.VariableName, 1024);
        status = check_var_size(efivars, attributes, varsize);
 
        if (status != EFI_SUCCESS) {
@@ -1223,7 +1167,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
 
                inode = NULL;
 
-               len = utf16_strlen(entry->var.VariableName);
+               len = ucs2_strlen(entry->var.VariableName);
 
                /* name, plus '-', plus GUID, plus NUL*/
                name = kmalloc(len + 1 + GUID_LEN + 1, GFP_ATOMIC);
@@ -1481,8 +1425,8 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
 
                if (efi_guidcmp(entry->var.VendorGuid, vendor))
                        continue;
-               if (utf16_strncmp(entry->var.VariableName, efi_name,
-                                 utf16_strlen(efi_name))) {
+               if (ucs2_strncmp(entry->var.VariableName, efi_name,
+                                 ucs2_strlen(efi_name))) {
                        /*
                         * Check if an old format,
                         * which doesn't support holding
@@ -1494,8 +1438,8 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
                        for (i = 0; i < DUMP_NAME_LEN; i++)
                                efi_name_old[i] = name_old[i];
 
-                       if (utf16_strncmp(entry->var.VariableName, efi_name_old,
-                                         utf16_strlen(efi_name_old)))
+                       if (ucs2_strncmp(entry->var.VariableName, efi_name_old,
+                                         ucs2_strlen(efi_name_old)))
                                continue;
                }
 
@@ -1573,8 +1517,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
         * Does this variable already exist?
         */
        list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
-               strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
-               strsize2 = utf16_strsize(new_var->VariableName, 1024);
+               strsize1 = ucs2_strsize(search_efivar->var.VariableName, 1024);
+               strsize2 = ucs2_strsize(new_var->VariableName, 1024);
                if (strsize1 == strsize2 &&
                        !memcmp(&(search_efivar->var.VariableName),
                                new_var->VariableName, strsize1) &&
@@ -1590,7 +1534,7 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
        }
 
        status = check_var_size_locked(efivars, new_var->Attributes,
-              new_var->DataSize + utf16_strsize(new_var->VariableName, 1024));
+              new_var->DataSize + ucs2_strsize(new_var->VariableName, 1024));
 
        if (status && status != EFI_UNSUPPORTED) {
                spin_unlock_irq(&efivars->lock);
@@ -1614,7 +1558,7 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
 
        /* Create the entry in sysfs.  Locking is not required here */
        status = efivar_create_sysfs_entry(efivars,
-                                          utf16_strsize(new_var->VariableName,
+                                          ucs2_strsize(new_var->VariableName,
                                                         1024),
                                           new_var->VariableName,
                                           &new_var->VendorGuid);
@@ -1644,8 +1588,8 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
         * Does this variable already exist?
         */
        list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
-               strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
-               strsize2 = utf16_strsize(del_var->VariableName, 1024);
+               strsize1 = ucs2_strsize(search_efivar->var.VariableName, 1024);
+               strsize2 = ucs2_strsize(del_var->VariableName, 1024);
                if (strsize1 == strsize2 &&
                        !memcmp(&(search_efivar->var.VariableName),
                                del_var->VariableName, strsize1) &&
@@ -1691,9 +1635,9 @@ static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor)
        unsigned long strsize1, strsize2;
        bool found = false;
 
-       strsize1 = utf16_strsize(variable_name, 1024);
+       strsize1 = ucs2_strsize(variable_name, 1024);
        list_for_each_entry_safe(entry, n, &efivars->list, list) {
-               strsize2 = utf16_strsize(entry->var.VariableName, 1024);
+               strsize2 = ucs2_strsize(entry->var.VariableName, 1024);
                if (strsize1 == strsize2 &&
                        !memcmp(variable_name, &(entry->var.VariableName),
                                strsize2) &&
@@ -2131,7 +2075,7 @@ efivars_init(void)
        ops.get_variable = efi.get_variable;
        ops.set_variable = efi.set_variable;
        ops.get_next_variable = efi.get_next_variable;
-       ops.query_variable_info = efi.query_variable_info;
+       ops.query_variable_store = efi_query_variable_store;
 
        error = register_efivars(&__efivars, &ops, efi_kobj);
        if (error)
index 9bf2f1f..3d7df3d 100644 (file)
@@ -333,6 +333,7 @@ typedef efi_status_t efi_query_capsule_caps_t(efi_capsule_header_t **capsules,
                                              unsigned long count,
                                              u64 *max_size,
                                              int *reset_type);
+typedef efi_status_t efi_query_variable_store_t(u32 attributes, unsigned long size);
 
 /*
  *  EFI Configuration Table and GUID definitions
@@ -575,9 +576,15 @@ extern void efi_enter_virtual_mode (void); /* switch EFI to virtual mode, if pos
 #ifdef CONFIG_X86
 extern void efi_late_init(void);
 extern void efi_free_boot_services(void);
+extern efi_status_t efi_query_variable_store(u32 attributes, unsigned long size);
 #else
 static inline void efi_late_init(void) {}
 static inline void efi_free_boot_services(void) {}
+
+static inline efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
+{
+       return EFI_SUCCESS;
+}
 #endif
 extern void __iomem *efi_lookup_mapped_addr(u64 phys_addr);
 extern u64 efi_get_iobase (void);
@@ -731,7 +738,7 @@ struct efivar_operations {
        efi_get_variable_t *get_variable;
        efi_get_next_variable_t *get_next_variable;
        efi_set_variable_t *set_variable;
-       efi_query_variable_info_t *query_variable_info;
+       efi_query_variable_store_t *query_variable_store;
 };
 
 struct efivars {
diff --git a/include/linux/ucs2_string.h b/include/linux/ucs2_string.h
new file mode 100644 (file)
index 0000000..cbb20af
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef _LINUX_UCS2_STRING_H_
+#define _LINUX_UCS2_STRING_H_
+
+#include <linux/types.h>       /* for size_t */
+#include <linux/stddef.h>      /* for NULL */
+
+typedef u16 ucs2_char_t;
+
+unsigned long ucs2_strnlen(const ucs2_char_t *s, size_t maxlength);
+unsigned long ucs2_strlen(const ucs2_char_t *s);
+unsigned long ucs2_strsize(const ucs2_char_t *data, unsigned long maxlength);
+int ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len);
+
+#endif /* _LINUX_UCS2_STRING_H_ */
index 3958dc4..fe01d41 100644 (file)
@@ -404,4 +404,7 @@ config OID_REGISTRY
        help
          Enable fast lookup object identifier registry.
 
+config UCS2_STRING
+        tristate
+
 endmenu
index d7946ff..6e2cc56 100644 (file)
@@ -174,3 +174,5 @@ quiet_cmd_build_OID_registry = GEN     $@
       cmd_build_OID_registry = perl $(srctree)/$(src)/build_OID_registry $< $@
 
 clean-files    += oid_registry_data.c
+
+obj-$(CONFIG_UCS2_STRING) += ucs2_string.o
diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c
new file mode 100644 (file)
index 0000000..6f500ef
--- /dev/null
@@ -0,0 +1,51 @@
+#include <linux/ucs2_string.h>
+#include <linux/module.h>
+
+/* Return the number of unicode characters in data */
+unsigned long
+ucs2_strnlen(const ucs2_char_t *s, size_t maxlength)
+{
+        unsigned long length = 0;
+
+        while (*s++ != 0 && length < maxlength)
+                length++;
+        return length;
+}
+EXPORT_SYMBOL(ucs2_strnlen);
+
+unsigned long
+ucs2_strlen(const ucs2_char_t *s)
+{
+        return ucs2_strnlen(s, ~0UL);
+}
+EXPORT_SYMBOL(ucs2_strlen);
+
+/*
+ * Return the number of bytes is the length of this string
+ * Note: this is NOT the same as the number of unicode characters
+ */
+unsigned long
+ucs2_strsize(const ucs2_char_t *data, unsigned long maxlength)
+{
+        return ucs2_strnlen(data, maxlength/sizeof(ucs2_char_t)) * sizeof(ucs2_char_t);
+}
+EXPORT_SYMBOL(ucs2_strsize);
+
+int
+ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len)
+{
+        while (1) {
+                if (len == 0)
+                        return 0;
+                if (*a < *b)
+                        return -1;
+                if (*a > *b)
+                        return 1;
+                if (*a == 0) /* implies *b == 0 */
+                        return 0;
+                a++;
+                b++;
+                len--;
+        }
+}
+EXPORT_SYMBOL(ucs2_strncmp);