162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * dell_rbu.c 462306a36Sopenharmony_ci * Bios Update driver for Dell systems 562306a36Sopenharmony_ci * Author: Dell Inc 662306a36Sopenharmony_ci * Abhay Salunke <abhay_salunke@dell.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright (C) 2005 Dell Inc. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by 1162306a36Sopenharmony_ci * creating entries in the /sys file systems on Linux 2.6 and higher 1262306a36Sopenharmony_ci * kernels. The driver supports two mechanism to update the BIOS namely 1362306a36Sopenharmony_ci * contiguous and packetized. Both these methods still require having some 1462306a36Sopenharmony_ci * application to set the CMOS bit indicating the BIOS to update itself 1562306a36Sopenharmony_ci * after a reboot. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Contiguous method: 1862306a36Sopenharmony_ci * This driver writes the incoming data in a monolithic image by allocating 1962306a36Sopenharmony_ci * contiguous physical pages large enough to accommodate the incoming BIOS 2062306a36Sopenharmony_ci * image size. 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * Packetized method: 2362306a36Sopenharmony_ci * The driver writes the incoming packet image by allocating a new packet 2462306a36Sopenharmony_ci * on every time the packet data is written. This driver requires an 2562306a36Sopenharmony_ci * application to break the BIOS image in to fixed sized packet chunks. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * See Documentation/admin-guide/dell_rbu.rst for more info. 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#include <linux/init.h> 3362306a36Sopenharmony_ci#include <linux/module.h> 3462306a36Sopenharmony_ci#include <linux/slab.h> 3562306a36Sopenharmony_ci#include <linux/string.h> 3662306a36Sopenharmony_ci#include <linux/errno.h> 3762306a36Sopenharmony_ci#include <linux/blkdev.h> 3862306a36Sopenharmony_ci#include <linux/platform_device.h> 3962306a36Sopenharmony_ci#include <linux/spinlock.h> 4062306a36Sopenharmony_ci#include <linux/moduleparam.h> 4162306a36Sopenharmony_ci#include <linux/firmware.h> 4262306a36Sopenharmony_ci#include <linux/dma-mapping.h> 4362306a36Sopenharmony_ci#include <asm/set_memory.h> 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ciMODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>"); 4662306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems"); 4762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 4862306a36Sopenharmony_ciMODULE_VERSION("3.2"); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define BIOS_SCAN_LIMIT 0xffffffff 5162306a36Sopenharmony_ci#define MAX_IMAGE_LENGTH 16 5262306a36Sopenharmony_cistatic struct _rbu_data { 5362306a36Sopenharmony_ci void *image_update_buffer; 5462306a36Sopenharmony_ci unsigned long image_update_buffer_size; 5562306a36Sopenharmony_ci unsigned long bios_image_size; 5662306a36Sopenharmony_ci int image_update_ordernum; 5762306a36Sopenharmony_ci spinlock_t lock; 5862306a36Sopenharmony_ci unsigned long packet_read_count; 5962306a36Sopenharmony_ci unsigned long num_packets; 6062306a36Sopenharmony_ci unsigned long packetsize; 6162306a36Sopenharmony_ci unsigned long imagesize; 6262306a36Sopenharmony_ci int entry_created; 6362306a36Sopenharmony_ci} rbu_data; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic char image_type[MAX_IMAGE_LENGTH + 1] = "mono"; 6662306a36Sopenharmony_cimodule_param_string(image_type, image_type, sizeof (image_type), 0); 6762306a36Sopenharmony_ciMODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet or init"); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic unsigned long allocation_floor = 0x100000; 7062306a36Sopenharmony_cimodule_param(allocation_floor, ulong, 0644); 7162306a36Sopenharmony_ciMODULE_PARM_DESC(allocation_floor, "Minimum address for allocations when using Packet mode"); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistruct packet_data { 7462306a36Sopenharmony_ci struct list_head list; 7562306a36Sopenharmony_ci size_t length; 7662306a36Sopenharmony_ci void *data; 7762306a36Sopenharmony_ci int ordernum; 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic struct packet_data packet_data_head; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic struct platform_device *rbu_device; 8362306a36Sopenharmony_cistatic int context; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic void init_packet_head(void) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci INIT_LIST_HEAD(&packet_data_head.list); 8862306a36Sopenharmony_ci rbu_data.packet_read_count = 0; 8962306a36Sopenharmony_ci rbu_data.num_packets = 0; 9062306a36Sopenharmony_ci rbu_data.packetsize = 0; 9162306a36Sopenharmony_ci rbu_data.imagesize = 0; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic int create_packet(void *data, size_t length) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct packet_data *newpacket; 9762306a36Sopenharmony_ci int ordernum = 0; 9862306a36Sopenharmony_ci int retval = 0; 9962306a36Sopenharmony_ci unsigned int packet_array_size = 0; 10062306a36Sopenharmony_ci void **invalid_addr_packet_array = NULL; 10162306a36Sopenharmony_ci void *packet_data_temp_buf = NULL; 10262306a36Sopenharmony_ci unsigned int idx = 0; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci pr_debug("entry\n"); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (!rbu_data.packetsize) { 10762306a36Sopenharmony_ci pr_debug("packetsize not specified\n"); 10862306a36Sopenharmony_ci retval = -EINVAL; 10962306a36Sopenharmony_ci goto out_noalloc; 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (!newpacket) { 11762306a36Sopenharmony_ci pr_warn("failed to allocate new packet\n"); 11862306a36Sopenharmony_ci retval = -ENOMEM; 11962306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 12062306a36Sopenharmony_ci goto out_noalloc; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci ordernum = get_order(length); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* 12662306a36Sopenharmony_ci * BIOS errata mean we cannot allocate packets below 1MB or they will 12762306a36Sopenharmony_ci * be overwritten by BIOS. 12862306a36Sopenharmony_ci * 12962306a36Sopenharmony_ci * array to temporarily hold packets 13062306a36Sopenharmony_ci * that are below the allocation floor 13162306a36Sopenharmony_ci * 13262306a36Sopenharmony_ci * NOTE: very simplistic because we only need the floor to be at 1MB 13362306a36Sopenharmony_ci * due to BIOS errata. This shouldn't be used for higher floors 13462306a36Sopenharmony_ci * or you will run out of mem trying to allocate the array. 13562306a36Sopenharmony_ci */ 13662306a36Sopenharmony_ci packet_array_size = max_t(unsigned int, allocation_floor / rbu_data.packetsize, 1); 13762306a36Sopenharmony_ci invalid_addr_packet_array = kcalloc(packet_array_size, sizeof(void *), 13862306a36Sopenharmony_ci GFP_KERNEL); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (!invalid_addr_packet_array) { 14162306a36Sopenharmony_ci pr_warn("failed to allocate invalid_addr_packet_array\n"); 14262306a36Sopenharmony_ci retval = -ENOMEM; 14362306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 14462306a36Sopenharmony_ci goto out_alloc_packet; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci while (!packet_data_temp_buf) { 14862306a36Sopenharmony_ci packet_data_temp_buf = (unsigned char *) 14962306a36Sopenharmony_ci __get_free_pages(GFP_KERNEL, ordernum); 15062306a36Sopenharmony_ci if (!packet_data_temp_buf) { 15162306a36Sopenharmony_ci pr_warn("failed to allocate new packet\n"); 15262306a36Sopenharmony_ci retval = -ENOMEM; 15362306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 15462306a36Sopenharmony_ci goto out_alloc_packet_array; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if ((unsigned long)virt_to_phys(packet_data_temp_buf) 15862306a36Sopenharmony_ci < allocation_floor) { 15962306a36Sopenharmony_ci pr_debug("packet 0x%lx below floor at 0x%lx\n", 16062306a36Sopenharmony_ci (unsigned long)virt_to_phys( 16162306a36Sopenharmony_ci packet_data_temp_buf), 16262306a36Sopenharmony_ci allocation_floor); 16362306a36Sopenharmony_ci invalid_addr_packet_array[idx++] = packet_data_temp_buf; 16462306a36Sopenharmony_ci packet_data_temp_buf = NULL; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci /* 16862306a36Sopenharmony_ci * set to uncachable or it may never get written back before reboot 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_ci set_memory_uc((unsigned long)packet_data_temp_buf, 1 << ordernum); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci newpacket->data = packet_data_temp_buf; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci pr_debug("newpacket at physical addr %lx\n", 17762306a36Sopenharmony_ci (unsigned long)virt_to_phys(newpacket->data)); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* packets may not have fixed size */ 18062306a36Sopenharmony_ci newpacket->length = length; 18162306a36Sopenharmony_ci newpacket->ordernum = ordernum; 18262306a36Sopenharmony_ci ++rbu_data.num_packets; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci /* initialize the newly created packet headers */ 18562306a36Sopenharmony_ci INIT_LIST_HEAD(&newpacket->list); 18662306a36Sopenharmony_ci list_add_tail(&newpacket->list, &packet_data_head.list); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci memcpy(newpacket->data, data, length); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci pr_debug("exit\n"); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ciout_alloc_packet_array: 19362306a36Sopenharmony_ci /* always free packet array */ 19462306a36Sopenharmony_ci while (idx--) { 19562306a36Sopenharmony_ci pr_debug("freeing unused packet below floor 0x%lx\n", 19662306a36Sopenharmony_ci (unsigned long)virt_to_phys(invalid_addr_packet_array[idx])); 19762306a36Sopenharmony_ci free_pages((unsigned long)invalid_addr_packet_array[idx], ordernum); 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci kfree(invalid_addr_packet_array); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ciout_alloc_packet: 20262306a36Sopenharmony_ci /* if error, free data */ 20362306a36Sopenharmony_ci if (retval) 20462306a36Sopenharmony_ci kfree(newpacket); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ciout_noalloc: 20762306a36Sopenharmony_ci return retval; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic int packetize_data(const u8 *data, size_t length) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci int rc = 0; 21362306a36Sopenharmony_ci int done = 0; 21462306a36Sopenharmony_ci int packet_length; 21562306a36Sopenharmony_ci u8 *temp; 21662306a36Sopenharmony_ci u8 *end = (u8 *) data + length; 21762306a36Sopenharmony_ci pr_debug("data length %zd\n", length); 21862306a36Sopenharmony_ci if (!rbu_data.packetsize) { 21962306a36Sopenharmony_ci pr_warn("packetsize not specified\n"); 22062306a36Sopenharmony_ci return -EIO; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci temp = (u8 *) data; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* packetize the hunk */ 22662306a36Sopenharmony_ci while (!done) { 22762306a36Sopenharmony_ci if ((temp + rbu_data.packetsize) < end) 22862306a36Sopenharmony_ci packet_length = rbu_data.packetsize; 22962306a36Sopenharmony_ci else { 23062306a36Sopenharmony_ci /* this is the last packet */ 23162306a36Sopenharmony_ci packet_length = end - temp; 23262306a36Sopenharmony_ci done = 1; 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci if ((rc = create_packet(temp, packet_length))) 23662306a36Sopenharmony_ci return rc; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci pr_debug("%p:%td\n", temp, (end - temp)); 23962306a36Sopenharmony_ci temp += packet_length; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci rbu_data.imagesize = length; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci return rc; 24562306a36Sopenharmony_ci} 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_cistatic int do_packet_read(char *data, struct packet_data *newpacket, 24862306a36Sopenharmony_ci int length, int bytes_read, int *list_read_count) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci void *ptemp_buf; 25162306a36Sopenharmony_ci int bytes_copied = 0; 25262306a36Sopenharmony_ci int j = 0; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci *list_read_count += newpacket->length; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (*list_read_count > bytes_read) { 25762306a36Sopenharmony_ci /* point to the start of unread data */ 25862306a36Sopenharmony_ci j = newpacket->length - (*list_read_count - bytes_read); 25962306a36Sopenharmony_ci /* point to the offset in the packet buffer */ 26062306a36Sopenharmony_ci ptemp_buf = (u8 *) newpacket->data + j; 26162306a36Sopenharmony_ci /* 26262306a36Sopenharmony_ci * check if there is enough room in 26362306a36Sopenharmony_ci * * the incoming buffer 26462306a36Sopenharmony_ci */ 26562306a36Sopenharmony_ci if (length > (*list_read_count - bytes_read)) 26662306a36Sopenharmony_ci /* 26762306a36Sopenharmony_ci * copy what ever is there in this 26862306a36Sopenharmony_ci * packet and move on 26962306a36Sopenharmony_ci */ 27062306a36Sopenharmony_ci bytes_copied = (*list_read_count - bytes_read); 27162306a36Sopenharmony_ci else 27262306a36Sopenharmony_ci /* copy the remaining */ 27362306a36Sopenharmony_ci bytes_copied = length; 27462306a36Sopenharmony_ci memcpy(data, ptemp_buf, bytes_copied); 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci return bytes_copied; 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic int packet_read_list(char *data, size_t * pread_length) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci struct packet_data *newpacket; 28262306a36Sopenharmony_ci int temp_count = 0; 28362306a36Sopenharmony_ci int bytes_copied = 0; 28462306a36Sopenharmony_ci int bytes_read = 0; 28562306a36Sopenharmony_ci int remaining_bytes = 0; 28662306a36Sopenharmony_ci char *pdest = data; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci /* check if we have any packets */ 28962306a36Sopenharmony_ci if (0 == rbu_data.num_packets) 29062306a36Sopenharmony_ci return -ENOMEM; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci remaining_bytes = *pread_length; 29362306a36Sopenharmony_ci bytes_read = rbu_data.packet_read_count; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci list_for_each_entry(newpacket, (&packet_data_head.list)->next, list) { 29662306a36Sopenharmony_ci bytes_copied = do_packet_read(pdest, newpacket, 29762306a36Sopenharmony_ci remaining_bytes, bytes_read, &temp_count); 29862306a36Sopenharmony_ci remaining_bytes -= bytes_copied; 29962306a36Sopenharmony_ci bytes_read += bytes_copied; 30062306a36Sopenharmony_ci pdest += bytes_copied; 30162306a36Sopenharmony_ci /* 30262306a36Sopenharmony_ci * check if we reached end of buffer before reaching the 30362306a36Sopenharmony_ci * last packet 30462306a36Sopenharmony_ci */ 30562306a36Sopenharmony_ci if (remaining_bytes == 0) 30662306a36Sopenharmony_ci break; 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci /*finally set the bytes read */ 30962306a36Sopenharmony_ci *pread_length = bytes_read - rbu_data.packet_read_count; 31062306a36Sopenharmony_ci rbu_data.packet_read_count = bytes_read; 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic void packet_empty_list(void) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci struct packet_data *newpacket, *tmp; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci list_for_each_entry_safe(newpacket, tmp, (&packet_data_head.list)->next, list) { 31962306a36Sopenharmony_ci list_del(&newpacket->list); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci /* 32262306a36Sopenharmony_ci * zero out the RBU packet memory before freeing 32362306a36Sopenharmony_ci * to make sure there are no stale RBU packets left in memory 32462306a36Sopenharmony_ci */ 32562306a36Sopenharmony_ci memset(newpacket->data, 0, rbu_data.packetsize); 32662306a36Sopenharmony_ci set_memory_wb((unsigned long)newpacket->data, 32762306a36Sopenharmony_ci 1 << newpacket->ordernum); 32862306a36Sopenharmony_ci free_pages((unsigned long) newpacket->data, 32962306a36Sopenharmony_ci newpacket->ordernum); 33062306a36Sopenharmony_ci kfree(newpacket); 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci rbu_data.packet_read_count = 0; 33362306a36Sopenharmony_ci rbu_data.num_packets = 0; 33462306a36Sopenharmony_ci rbu_data.imagesize = 0; 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci/* 33862306a36Sopenharmony_ci * img_update_free: Frees the buffer allocated for storing BIOS image 33962306a36Sopenharmony_ci * Always called with lock held and returned with lock held 34062306a36Sopenharmony_ci */ 34162306a36Sopenharmony_cistatic void img_update_free(void) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci if (!rbu_data.image_update_buffer) 34462306a36Sopenharmony_ci return; 34562306a36Sopenharmony_ci /* 34662306a36Sopenharmony_ci * zero out this buffer before freeing it to get rid of any stale 34762306a36Sopenharmony_ci * BIOS image copied in memory. 34862306a36Sopenharmony_ci */ 34962306a36Sopenharmony_ci memset(rbu_data.image_update_buffer, 0, 35062306a36Sopenharmony_ci rbu_data.image_update_buffer_size); 35162306a36Sopenharmony_ci free_pages((unsigned long) rbu_data.image_update_buffer, 35262306a36Sopenharmony_ci rbu_data.image_update_ordernum); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci /* 35562306a36Sopenharmony_ci * Re-initialize the rbu_data variables after a free 35662306a36Sopenharmony_ci */ 35762306a36Sopenharmony_ci rbu_data.image_update_ordernum = -1; 35862306a36Sopenharmony_ci rbu_data.image_update_buffer = NULL; 35962306a36Sopenharmony_ci rbu_data.image_update_buffer_size = 0; 36062306a36Sopenharmony_ci rbu_data.bios_image_size = 0; 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci/* 36462306a36Sopenharmony_ci * img_update_realloc: This function allocates the contiguous pages to 36562306a36Sopenharmony_ci * accommodate the requested size of data. The memory address and size 36662306a36Sopenharmony_ci * values are stored globally and on every call to this function the new 36762306a36Sopenharmony_ci * size is checked to see if more data is required than the existing size. 36862306a36Sopenharmony_ci * If true the previous memory is freed and new allocation is done to 36962306a36Sopenharmony_ci * accommodate the new size. If the incoming size is less then than the 37062306a36Sopenharmony_ci * already allocated size, then that memory is reused. This function is 37162306a36Sopenharmony_ci * called with lock held and returns with lock held. 37262306a36Sopenharmony_ci */ 37362306a36Sopenharmony_cistatic int img_update_realloc(unsigned long size) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci unsigned char *image_update_buffer = NULL; 37662306a36Sopenharmony_ci unsigned long img_buf_phys_addr; 37762306a36Sopenharmony_ci int ordernum; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci /* 38062306a36Sopenharmony_ci * check if the buffer of sufficient size has been 38162306a36Sopenharmony_ci * already allocated 38262306a36Sopenharmony_ci */ 38362306a36Sopenharmony_ci if (rbu_data.image_update_buffer_size >= size) { 38462306a36Sopenharmony_ci /* 38562306a36Sopenharmony_ci * check for corruption 38662306a36Sopenharmony_ci */ 38762306a36Sopenharmony_ci if ((size != 0) && (rbu_data.image_update_buffer == NULL)) { 38862306a36Sopenharmony_ci pr_err("corruption check failed\n"); 38962306a36Sopenharmony_ci return -EINVAL; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci /* 39262306a36Sopenharmony_ci * we have a valid pre-allocated buffer with 39362306a36Sopenharmony_ci * sufficient size 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_ci return 0; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci /* 39962306a36Sopenharmony_ci * free any previously allocated buffer 40062306a36Sopenharmony_ci */ 40162306a36Sopenharmony_ci img_update_free(); 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci ordernum = get_order(size); 40662306a36Sopenharmony_ci image_update_buffer = 40762306a36Sopenharmony_ci (unsigned char *)__get_free_pages(GFP_DMA32, ordernum); 40862306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 40962306a36Sopenharmony_ci if (!image_update_buffer) { 41062306a36Sopenharmony_ci pr_debug("Not enough memory for image update: size = %ld\n", size); 41162306a36Sopenharmony_ci return -ENOMEM; 41262306a36Sopenharmony_ci } 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci img_buf_phys_addr = (unsigned long)virt_to_phys(image_update_buffer); 41562306a36Sopenharmony_ci if (WARN_ON_ONCE(img_buf_phys_addr > BIOS_SCAN_LIMIT)) 41662306a36Sopenharmony_ci return -EINVAL; /* can't happen per definition */ 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci rbu_data.image_update_buffer = image_update_buffer; 41962306a36Sopenharmony_ci rbu_data.image_update_buffer_size = size; 42062306a36Sopenharmony_ci rbu_data.bios_image_size = rbu_data.image_update_buffer_size; 42162306a36Sopenharmony_ci rbu_data.image_update_ordernum = ordernum; 42262306a36Sopenharmony_ci return 0; 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_cistatic ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci int retval; 42862306a36Sopenharmony_ci size_t bytes_left; 42962306a36Sopenharmony_ci size_t data_length; 43062306a36Sopenharmony_ci char *ptempBuf = buffer; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci /* check to see if we have something to return */ 43362306a36Sopenharmony_ci if (rbu_data.num_packets == 0) { 43462306a36Sopenharmony_ci pr_debug("no packets written\n"); 43562306a36Sopenharmony_ci retval = -ENOMEM; 43662306a36Sopenharmony_ci goto read_rbu_data_exit; 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci if (pos > rbu_data.imagesize) { 44062306a36Sopenharmony_ci retval = 0; 44162306a36Sopenharmony_ci pr_warn("data underrun\n"); 44262306a36Sopenharmony_ci goto read_rbu_data_exit; 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci bytes_left = rbu_data.imagesize - pos; 44662306a36Sopenharmony_ci data_length = min(bytes_left, count); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) 44962306a36Sopenharmony_ci goto read_rbu_data_exit; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci if ((pos + count) > rbu_data.imagesize) { 45262306a36Sopenharmony_ci rbu_data.packet_read_count = 0; 45362306a36Sopenharmony_ci /* this was the last copy */ 45462306a36Sopenharmony_ci retval = bytes_left; 45562306a36Sopenharmony_ci } else 45662306a36Sopenharmony_ci retval = count; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci read_rbu_data_exit: 45962306a36Sopenharmony_ci return retval; 46062306a36Sopenharmony_ci} 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_cistatic ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) 46362306a36Sopenharmony_ci{ 46462306a36Sopenharmony_ci /* check to see if we have something to return */ 46562306a36Sopenharmony_ci if ((rbu_data.image_update_buffer == NULL) || 46662306a36Sopenharmony_ci (rbu_data.bios_image_size == 0)) { 46762306a36Sopenharmony_ci pr_debug("image_update_buffer %p, bios_image_size %lu\n", 46862306a36Sopenharmony_ci rbu_data.image_update_buffer, 46962306a36Sopenharmony_ci rbu_data.bios_image_size); 47062306a36Sopenharmony_ci return -ENOMEM; 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci return memory_read_from_buffer(buffer, count, &pos, 47462306a36Sopenharmony_ci rbu_data.image_update_buffer, rbu_data.bios_image_size); 47562306a36Sopenharmony_ci} 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_cistatic ssize_t data_read(struct file *filp, struct kobject *kobj, 47862306a36Sopenharmony_ci struct bin_attribute *bin_attr, 47962306a36Sopenharmony_ci char *buffer, loff_t pos, size_t count) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci ssize_t ret_count = 0; 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (!strcmp(image_type, "mono")) 48662306a36Sopenharmony_ci ret_count = read_rbu_mono_data(buffer, pos, count); 48762306a36Sopenharmony_ci else if (!strcmp(image_type, "packet")) 48862306a36Sopenharmony_ci ret_count = read_packet_data(buffer, pos, count); 48962306a36Sopenharmony_ci else 49062306a36Sopenharmony_ci pr_debug("invalid image type specified\n"); 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 49362306a36Sopenharmony_ci return ret_count; 49462306a36Sopenharmony_ci} 49562306a36Sopenharmony_cistatic BIN_ATTR_RO(data, 0); 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_cistatic void callbackfn_rbu(const struct firmware *fw, void *context) 49862306a36Sopenharmony_ci{ 49962306a36Sopenharmony_ci rbu_data.entry_created = 0; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci if (!fw) 50262306a36Sopenharmony_ci return; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci if (!fw->size) 50562306a36Sopenharmony_ci goto out; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 50862306a36Sopenharmony_ci if (!strcmp(image_type, "mono")) { 50962306a36Sopenharmony_ci if (!img_update_realloc(fw->size)) 51062306a36Sopenharmony_ci memcpy(rbu_data.image_update_buffer, 51162306a36Sopenharmony_ci fw->data, fw->size); 51262306a36Sopenharmony_ci } else if (!strcmp(image_type, "packet")) { 51362306a36Sopenharmony_ci /* 51462306a36Sopenharmony_ci * we need to free previous packets if a 51562306a36Sopenharmony_ci * new hunk of packets needs to be downloaded 51662306a36Sopenharmony_ci */ 51762306a36Sopenharmony_ci packet_empty_list(); 51862306a36Sopenharmony_ci if (packetize_data(fw->data, fw->size)) 51962306a36Sopenharmony_ci /* Incase something goes wrong when we are 52062306a36Sopenharmony_ci * in middle of packetizing the data, we 52162306a36Sopenharmony_ci * need to free up whatever packets might 52262306a36Sopenharmony_ci * have been created before we quit. 52362306a36Sopenharmony_ci */ 52462306a36Sopenharmony_ci packet_empty_list(); 52562306a36Sopenharmony_ci } else 52662306a36Sopenharmony_ci pr_debug("invalid image type specified\n"); 52762306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 52862306a36Sopenharmony_ci out: 52962306a36Sopenharmony_ci release_firmware(fw); 53062306a36Sopenharmony_ci} 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_cistatic ssize_t image_type_read(struct file *filp, struct kobject *kobj, 53362306a36Sopenharmony_ci struct bin_attribute *bin_attr, 53462306a36Sopenharmony_ci char *buffer, loff_t pos, size_t count) 53562306a36Sopenharmony_ci{ 53662306a36Sopenharmony_ci int size = 0; 53762306a36Sopenharmony_ci if (!pos) 53862306a36Sopenharmony_ci size = scnprintf(buffer, count, "%s\n", image_type); 53962306a36Sopenharmony_ci return size; 54062306a36Sopenharmony_ci} 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_cistatic ssize_t image_type_write(struct file *filp, struct kobject *kobj, 54362306a36Sopenharmony_ci struct bin_attribute *bin_attr, 54462306a36Sopenharmony_ci char *buffer, loff_t pos, size_t count) 54562306a36Sopenharmony_ci{ 54662306a36Sopenharmony_ci int rc = count; 54762306a36Sopenharmony_ci int req_firm_rc = 0; 54862306a36Sopenharmony_ci int i; 54962306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 55062306a36Sopenharmony_ci /* 55162306a36Sopenharmony_ci * Find the first newline or space 55262306a36Sopenharmony_ci */ 55362306a36Sopenharmony_ci for (i = 0; i < count; ++i) 55462306a36Sopenharmony_ci if (buffer[i] == '\n' || buffer[i] == ' ') { 55562306a36Sopenharmony_ci buffer[i] = '\0'; 55662306a36Sopenharmony_ci break; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci if (i == count) 55962306a36Sopenharmony_ci buffer[count] = '\0'; 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci if (strstr(buffer, "mono")) 56262306a36Sopenharmony_ci strcpy(image_type, "mono"); 56362306a36Sopenharmony_ci else if (strstr(buffer, "packet")) 56462306a36Sopenharmony_ci strcpy(image_type, "packet"); 56562306a36Sopenharmony_ci else if (strstr(buffer, "init")) { 56662306a36Sopenharmony_ci /* 56762306a36Sopenharmony_ci * If due to the user error the driver gets in a bad 56862306a36Sopenharmony_ci * state where even though it is loaded , the 56962306a36Sopenharmony_ci * /sys/class/firmware/dell_rbu entries are missing. 57062306a36Sopenharmony_ci * to cover this situation the user can recreate entries 57162306a36Sopenharmony_ci * by writing init to image_type. 57262306a36Sopenharmony_ci */ 57362306a36Sopenharmony_ci if (!rbu_data.entry_created) { 57462306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 57562306a36Sopenharmony_ci req_firm_rc = request_firmware_nowait(THIS_MODULE, 57662306a36Sopenharmony_ci FW_ACTION_NOUEVENT, "dell_rbu", 57762306a36Sopenharmony_ci &rbu_device->dev, GFP_KERNEL, &context, 57862306a36Sopenharmony_ci callbackfn_rbu); 57962306a36Sopenharmony_ci if (req_firm_rc) { 58062306a36Sopenharmony_ci pr_err("request_firmware_nowait failed %d\n", rc); 58162306a36Sopenharmony_ci rc = -EIO; 58262306a36Sopenharmony_ci } else 58362306a36Sopenharmony_ci rbu_data.entry_created = 1; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 58662306a36Sopenharmony_ci } 58762306a36Sopenharmony_ci } else { 58862306a36Sopenharmony_ci pr_warn("image_type is invalid\n"); 58962306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 59062306a36Sopenharmony_ci return -EINVAL; 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci /* we must free all previous allocations */ 59462306a36Sopenharmony_ci packet_empty_list(); 59562306a36Sopenharmony_ci img_update_free(); 59662306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci return rc; 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_cistatic BIN_ATTR_RW(image_type, 0); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_cistatic ssize_t packet_size_read(struct file *filp, struct kobject *kobj, 60362306a36Sopenharmony_ci struct bin_attribute *bin_attr, 60462306a36Sopenharmony_ci char *buffer, loff_t pos, size_t count) 60562306a36Sopenharmony_ci{ 60662306a36Sopenharmony_ci int size = 0; 60762306a36Sopenharmony_ci if (!pos) { 60862306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 60962306a36Sopenharmony_ci size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize); 61062306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 61162306a36Sopenharmony_ci } 61262306a36Sopenharmony_ci return size; 61362306a36Sopenharmony_ci} 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_cistatic ssize_t packet_size_write(struct file *filp, struct kobject *kobj, 61662306a36Sopenharmony_ci struct bin_attribute *bin_attr, 61762306a36Sopenharmony_ci char *buffer, loff_t pos, size_t count) 61862306a36Sopenharmony_ci{ 61962306a36Sopenharmony_ci unsigned long temp; 62062306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 62162306a36Sopenharmony_ci packet_empty_list(); 62262306a36Sopenharmony_ci sscanf(buffer, "%lu", &temp); 62362306a36Sopenharmony_ci if (temp < 0xffffffff) 62462306a36Sopenharmony_ci rbu_data.packetsize = temp; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 62762306a36Sopenharmony_ci return count; 62862306a36Sopenharmony_ci} 62962306a36Sopenharmony_cistatic BIN_ATTR_RW(packet_size, 0); 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_cistatic struct bin_attribute *rbu_bin_attrs[] = { 63262306a36Sopenharmony_ci &bin_attr_data, 63362306a36Sopenharmony_ci &bin_attr_image_type, 63462306a36Sopenharmony_ci &bin_attr_packet_size, 63562306a36Sopenharmony_ci NULL 63662306a36Sopenharmony_ci}; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_cistatic const struct attribute_group rbu_group = { 63962306a36Sopenharmony_ci .bin_attrs = rbu_bin_attrs, 64062306a36Sopenharmony_ci}; 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_cistatic int __init dcdrbu_init(void) 64362306a36Sopenharmony_ci{ 64462306a36Sopenharmony_ci int rc; 64562306a36Sopenharmony_ci spin_lock_init(&rbu_data.lock); 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci init_packet_head(); 64862306a36Sopenharmony_ci rbu_device = platform_device_register_simple("dell_rbu", PLATFORM_DEVID_NONE, NULL, 0); 64962306a36Sopenharmony_ci if (IS_ERR(rbu_device)) { 65062306a36Sopenharmony_ci pr_err("platform_device_register_simple failed\n"); 65162306a36Sopenharmony_ci return PTR_ERR(rbu_device); 65262306a36Sopenharmony_ci } 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci rc = sysfs_create_group(&rbu_device->dev.kobj, &rbu_group); 65562306a36Sopenharmony_ci if (rc) 65662306a36Sopenharmony_ci goto out_devreg; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci rbu_data.entry_created = 0; 65962306a36Sopenharmony_ci return 0; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ciout_devreg: 66262306a36Sopenharmony_ci platform_device_unregister(rbu_device); 66362306a36Sopenharmony_ci return rc; 66462306a36Sopenharmony_ci} 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_cistatic __exit void dcdrbu_exit(void) 66762306a36Sopenharmony_ci{ 66862306a36Sopenharmony_ci spin_lock(&rbu_data.lock); 66962306a36Sopenharmony_ci packet_empty_list(); 67062306a36Sopenharmony_ci img_update_free(); 67162306a36Sopenharmony_ci spin_unlock(&rbu_data.lock); 67262306a36Sopenharmony_ci sysfs_remove_group(&rbu_device->dev.kobj, &rbu_group); 67362306a36Sopenharmony_ci platform_device_unregister(rbu_device); 67462306a36Sopenharmony_ci} 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cimodule_exit(dcdrbu_exit); 67762306a36Sopenharmony_cimodule_init(dcdrbu_init); 678