18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * dell_rbu.c 48c2ecf20Sopenharmony_ci * Bios Update driver for Dell systems 58c2ecf20Sopenharmony_ci * Author: Dell Inc 68c2ecf20Sopenharmony_ci * Abhay Salunke <abhay_salunke@dell.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright (C) 2005 Dell Inc. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by 118c2ecf20Sopenharmony_ci * creating entries in the /sys file systems on Linux 2.6 and higher 128c2ecf20Sopenharmony_ci * kernels. The driver supports two mechanism to update the BIOS namely 138c2ecf20Sopenharmony_ci * contiguous and packetized. Both these methods still require having some 148c2ecf20Sopenharmony_ci * application to set the CMOS bit indicating the BIOS to update itself 158c2ecf20Sopenharmony_ci * after a reboot. 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * Contiguous method: 188c2ecf20Sopenharmony_ci * This driver writes the incoming data in a monolithic image by allocating 198c2ecf20Sopenharmony_ci * contiguous physical pages large enough to accommodate the incoming BIOS 208c2ecf20Sopenharmony_ci * image size. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * Packetized method: 238c2ecf20Sopenharmony_ci * The driver writes the incoming packet image by allocating a new packet 248c2ecf20Sopenharmony_ci * on every time the packet data is written. This driver requires an 258c2ecf20Sopenharmony_ci * application to break the BIOS image in to fixed sized packet chunks. 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * See Documentation/admin-guide/dell_rbu.rst for more info. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#include <linux/init.h> 338c2ecf20Sopenharmony_ci#include <linux/module.h> 348c2ecf20Sopenharmony_ci#include <linux/slab.h> 358c2ecf20Sopenharmony_ci#include <linux/string.h> 368c2ecf20Sopenharmony_ci#include <linux/errno.h> 378c2ecf20Sopenharmony_ci#include <linux/blkdev.h> 388c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 398c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 408c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 418c2ecf20Sopenharmony_ci#include <linux/firmware.h> 428c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 438c2ecf20Sopenharmony_ci#include <asm/set_memory.h> 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ciMODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>"); 468c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems"); 478c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 488c2ecf20Sopenharmony_ciMODULE_VERSION("3.2"); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#define BIOS_SCAN_LIMIT 0xffffffff 518c2ecf20Sopenharmony_ci#define MAX_IMAGE_LENGTH 16 528c2ecf20Sopenharmony_cistatic struct _rbu_data { 538c2ecf20Sopenharmony_ci void *image_update_buffer; 548c2ecf20Sopenharmony_ci unsigned long image_update_buffer_size; 558c2ecf20Sopenharmony_ci unsigned long bios_image_size; 568c2ecf20Sopenharmony_ci int image_update_ordernum; 578c2ecf20Sopenharmony_ci spinlock_t lock; 588c2ecf20Sopenharmony_ci unsigned long packet_read_count; 598c2ecf20Sopenharmony_ci unsigned long num_packets; 608c2ecf20Sopenharmony_ci unsigned long packetsize; 618c2ecf20Sopenharmony_ci unsigned long imagesize; 628c2ecf20Sopenharmony_ci int entry_created; 638c2ecf20Sopenharmony_ci} rbu_data; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic char image_type[MAX_IMAGE_LENGTH + 1] = "mono"; 668c2ecf20Sopenharmony_cimodule_param_string(image_type, image_type, sizeof (image_type), 0); 678c2ecf20Sopenharmony_ciMODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet or init"); 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic unsigned long allocation_floor = 0x100000; 708c2ecf20Sopenharmony_cimodule_param(allocation_floor, ulong, 0644); 718c2ecf20Sopenharmony_ciMODULE_PARM_DESC(allocation_floor, "Minimum address for allocations when using Packet mode"); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistruct packet_data { 748c2ecf20Sopenharmony_ci struct list_head list; 758c2ecf20Sopenharmony_ci size_t length; 768c2ecf20Sopenharmony_ci void *data; 778c2ecf20Sopenharmony_ci int ordernum; 788c2ecf20Sopenharmony_ci}; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic struct packet_data packet_data_head; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic struct platform_device *rbu_device; 838c2ecf20Sopenharmony_cistatic int context; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic void init_packet_head(void) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&packet_data_head.list); 888c2ecf20Sopenharmony_ci rbu_data.packet_read_count = 0; 898c2ecf20Sopenharmony_ci rbu_data.num_packets = 0; 908c2ecf20Sopenharmony_ci rbu_data.packetsize = 0; 918c2ecf20Sopenharmony_ci rbu_data.imagesize = 0; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic int create_packet(void *data, size_t length) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct packet_data *newpacket; 978c2ecf20Sopenharmony_ci int ordernum = 0; 988c2ecf20Sopenharmony_ci int retval = 0; 998c2ecf20Sopenharmony_ci unsigned int packet_array_size = 0; 1008c2ecf20Sopenharmony_ci void **invalid_addr_packet_array = NULL; 1018c2ecf20Sopenharmony_ci void *packet_data_temp_buf = NULL; 1028c2ecf20Sopenharmony_ci unsigned int idx = 0; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci pr_debug("entry\n"); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci if (!rbu_data.packetsize) { 1078c2ecf20Sopenharmony_ci pr_debug("packetsize not specified\n"); 1088c2ecf20Sopenharmony_ci retval = -EINVAL; 1098c2ecf20Sopenharmony_ci goto out_noalloc; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (!newpacket) { 1178c2ecf20Sopenharmony_ci pr_warn("failed to allocate new packet\n"); 1188c2ecf20Sopenharmony_ci retval = -ENOMEM; 1198c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 1208c2ecf20Sopenharmony_ci goto out_noalloc; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci ordernum = get_order(length); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* 1268c2ecf20Sopenharmony_ci * BIOS errata mean we cannot allocate packets below 1MB or they will 1278c2ecf20Sopenharmony_ci * be overwritten by BIOS. 1288c2ecf20Sopenharmony_ci * 1298c2ecf20Sopenharmony_ci * array to temporarily hold packets 1308c2ecf20Sopenharmony_ci * that are below the allocation floor 1318c2ecf20Sopenharmony_ci * 1328c2ecf20Sopenharmony_ci * NOTE: very simplistic because we only need the floor to be at 1MB 1338c2ecf20Sopenharmony_ci * due to BIOS errata. This shouldn't be used for higher floors 1348c2ecf20Sopenharmony_ci * or you will run out of mem trying to allocate the array. 1358c2ecf20Sopenharmony_ci */ 1368c2ecf20Sopenharmony_ci packet_array_size = max_t(unsigned int, allocation_floor / rbu_data.packetsize, 1); 1378c2ecf20Sopenharmony_ci invalid_addr_packet_array = kcalloc(packet_array_size, sizeof(void *), 1388c2ecf20Sopenharmony_ci GFP_KERNEL); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (!invalid_addr_packet_array) { 1418c2ecf20Sopenharmony_ci pr_warn("failed to allocate invalid_addr_packet_array\n"); 1428c2ecf20Sopenharmony_ci retval = -ENOMEM; 1438c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 1448c2ecf20Sopenharmony_ci goto out_alloc_packet; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci while (!packet_data_temp_buf) { 1488c2ecf20Sopenharmony_ci packet_data_temp_buf = (unsigned char *) 1498c2ecf20Sopenharmony_ci __get_free_pages(GFP_KERNEL, ordernum); 1508c2ecf20Sopenharmony_ci if (!packet_data_temp_buf) { 1518c2ecf20Sopenharmony_ci pr_warn("failed to allocate new packet\n"); 1528c2ecf20Sopenharmony_ci retval = -ENOMEM; 1538c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 1548c2ecf20Sopenharmony_ci goto out_alloc_packet_array; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci if ((unsigned long)virt_to_phys(packet_data_temp_buf) 1588c2ecf20Sopenharmony_ci < allocation_floor) { 1598c2ecf20Sopenharmony_ci pr_debug("packet 0x%lx below floor at 0x%lx\n", 1608c2ecf20Sopenharmony_ci (unsigned long)virt_to_phys( 1618c2ecf20Sopenharmony_ci packet_data_temp_buf), 1628c2ecf20Sopenharmony_ci allocation_floor); 1638c2ecf20Sopenharmony_ci invalid_addr_packet_array[idx++] = packet_data_temp_buf; 1648c2ecf20Sopenharmony_ci packet_data_temp_buf = NULL; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci /* 1688c2ecf20Sopenharmony_ci * set to uncachable or it may never get written back before reboot 1698c2ecf20Sopenharmony_ci */ 1708c2ecf20Sopenharmony_ci set_memory_uc((unsigned long)packet_data_temp_buf, 1 << ordernum); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci newpacket->data = packet_data_temp_buf; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci pr_debug("newpacket at physical addr %lx\n", 1778c2ecf20Sopenharmony_ci (unsigned long)virt_to_phys(newpacket->data)); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci /* packets may not have fixed size */ 1808c2ecf20Sopenharmony_ci newpacket->length = length; 1818c2ecf20Sopenharmony_ci newpacket->ordernum = ordernum; 1828c2ecf20Sopenharmony_ci ++rbu_data.num_packets; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci /* initialize the newly created packet headers */ 1858c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&newpacket->list); 1868c2ecf20Sopenharmony_ci list_add_tail(&newpacket->list, &packet_data_head.list); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci memcpy(newpacket->data, data, length); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci pr_debug("exit\n"); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ciout_alloc_packet_array: 1938c2ecf20Sopenharmony_ci /* always free packet array */ 1948c2ecf20Sopenharmony_ci while (idx--) { 1958c2ecf20Sopenharmony_ci pr_debug("freeing unused packet below floor 0x%lx\n", 1968c2ecf20Sopenharmony_ci (unsigned long)virt_to_phys(invalid_addr_packet_array[idx])); 1978c2ecf20Sopenharmony_ci free_pages((unsigned long)invalid_addr_packet_array[idx], ordernum); 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci kfree(invalid_addr_packet_array); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ciout_alloc_packet: 2028c2ecf20Sopenharmony_ci /* if error, free data */ 2038c2ecf20Sopenharmony_ci if (retval) 2048c2ecf20Sopenharmony_ci kfree(newpacket); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ciout_noalloc: 2078c2ecf20Sopenharmony_ci return retval; 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistatic int packetize_data(const u8 *data, size_t length) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci int rc = 0; 2138c2ecf20Sopenharmony_ci int done = 0; 2148c2ecf20Sopenharmony_ci int packet_length; 2158c2ecf20Sopenharmony_ci u8 *temp; 2168c2ecf20Sopenharmony_ci u8 *end = (u8 *) data + length; 2178c2ecf20Sopenharmony_ci pr_debug("data length %zd\n", length); 2188c2ecf20Sopenharmony_ci if (!rbu_data.packetsize) { 2198c2ecf20Sopenharmony_ci pr_warn("packetsize not specified\n"); 2208c2ecf20Sopenharmony_ci return -EIO; 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci temp = (u8 *) data; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* packetize the hunk */ 2268c2ecf20Sopenharmony_ci while (!done) { 2278c2ecf20Sopenharmony_ci if ((temp + rbu_data.packetsize) < end) 2288c2ecf20Sopenharmony_ci packet_length = rbu_data.packetsize; 2298c2ecf20Sopenharmony_ci else { 2308c2ecf20Sopenharmony_ci /* this is the last packet */ 2318c2ecf20Sopenharmony_ci packet_length = end - temp; 2328c2ecf20Sopenharmony_ci done = 1; 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci if ((rc = create_packet(temp, packet_length))) 2368c2ecf20Sopenharmony_ci return rc; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci pr_debug("%p:%td\n", temp, (end - temp)); 2398c2ecf20Sopenharmony_ci temp += packet_length; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci rbu_data.imagesize = length; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci return rc; 2458c2ecf20Sopenharmony_ci} 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_cistatic int do_packet_read(char *data, struct packet_data *newpacket, 2488c2ecf20Sopenharmony_ci int length, int bytes_read, int *list_read_count) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci void *ptemp_buf; 2518c2ecf20Sopenharmony_ci int bytes_copied = 0; 2528c2ecf20Sopenharmony_ci int j = 0; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci *list_read_count += newpacket->length; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci if (*list_read_count > bytes_read) { 2578c2ecf20Sopenharmony_ci /* point to the start of unread data */ 2588c2ecf20Sopenharmony_ci j = newpacket->length - (*list_read_count - bytes_read); 2598c2ecf20Sopenharmony_ci /* point to the offset in the packet buffer */ 2608c2ecf20Sopenharmony_ci ptemp_buf = (u8 *) newpacket->data + j; 2618c2ecf20Sopenharmony_ci /* 2628c2ecf20Sopenharmony_ci * check if there is enough room in 2638c2ecf20Sopenharmony_ci * * the incoming buffer 2648c2ecf20Sopenharmony_ci */ 2658c2ecf20Sopenharmony_ci if (length > (*list_read_count - bytes_read)) 2668c2ecf20Sopenharmony_ci /* 2678c2ecf20Sopenharmony_ci * copy what ever is there in this 2688c2ecf20Sopenharmony_ci * packet and move on 2698c2ecf20Sopenharmony_ci */ 2708c2ecf20Sopenharmony_ci bytes_copied = (*list_read_count - bytes_read); 2718c2ecf20Sopenharmony_ci else 2728c2ecf20Sopenharmony_ci /* copy the remaining */ 2738c2ecf20Sopenharmony_ci bytes_copied = length; 2748c2ecf20Sopenharmony_ci memcpy(data, ptemp_buf, bytes_copied); 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci return bytes_copied; 2778c2ecf20Sopenharmony_ci} 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic int packet_read_list(char *data, size_t * pread_length) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci struct packet_data *newpacket; 2828c2ecf20Sopenharmony_ci int temp_count = 0; 2838c2ecf20Sopenharmony_ci int bytes_copied = 0; 2848c2ecf20Sopenharmony_ci int bytes_read = 0; 2858c2ecf20Sopenharmony_ci int remaining_bytes = 0; 2868c2ecf20Sopenharmony_ci char *pdest = data; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci /* check if we have any packets */ 2898c2ecf20Sopenharmony_ci if (0 == rbu_data.num_packets) 2908c2ecf20Sopenharmony_ci return -ENOMEM; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci remaining_bytes = *pread_length; 2938c2ecf20Sopenharmony_ci bytes_read = rbu_data.packet_read_count; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci list_for_each_entry(newpacket, (&packet_data_head.list)->next, list) { 2968c2ecf20Sopenharmony_ci bytes_copied = do_packet_read(pdest, newpacket, 2978c2ecf20Sopenharmony_ci remaining_bytes, bytes_read, &temp_count); 2988c2ecf20Sopenharmony_ci remaining_bytes -= bytes_copied; 2998c2ecf20Sopenharmony_ci bytes_read += bytes_copied; 3008c2ecf20Sopenharmony_ci pdest += bytes_copied; 3018c2ecf20Sopenharmony_ci /* 3028c2ecf20Sopenharmony_ci * check if we reached end of buffer before reaching the 3038c2ecf20Sopenharmony_ci * last packet 3048c2ecf20Sopenharmony_ci */ 3058c2ecf20Sopenharmony_ci if (remaining_bytes == 0) 3068c2ecf20Sopenharmony_ci break; 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci /*finally set the bytes read */ 3098c2ecf20Sopenharmony_ci *pread_length = bytes_read - rbu_data.packet_read_count; 3108c2ecf20Sopenharmony_ci rbu_data.packet_read_count = bytes_read; 3118c2ecf20Sopenharmony_ci return 0; 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_cistatic void packet_empty_list(void) 3158c2ecf20Sopenharmony_ci{ 3168c2ecf20Sopenharmony_ci struct packet_data *newpacket, *tmp; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci list_for_each_entry_safe(newpacket, tmp, (&packet_data_head.list)->next, list) { 3198c2ecf20Sopenharmony_ci list_del(&newpacket->list); 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci /* 3228c2ecf20Sopenharmony_ci * zero out the RBU packet memory before freeing 3238c2ecf20Sopenharmony_ci * to make sure there are no stale RBU packets left in memory 3248c2ecf20Sopenharmony_ci */ 3258c2ecf20Sopenharmony_ci memset(newpacket->data, 0, rbu_data.packetsize); 3268c2ecf20Sopenharmony_ci set_memory_wb((unsigned long)newpacket->data, 3278c2ecf20Sopenharmony_ci 1 << newpacket->ordernum); 3288c2ecf20Sopenharmony_ci free_pages((unsigned long) newpacket->data, 3298c2ecf20Sopenharmony_ci newpacket->ordernum); 3308c2ecf20Sopenharmony_ci kfree(newpacket); 3318c2ecf20Sopenharmony_ci } 3328c2ecf20Sopenharmony_ci rbu_data.packet_read_count = 0; 3338c2ecf20Sopenharmony_ci rbu_data.num_packets = 0; 3348c2ecf20Sopenharmony_ci rbu_data.imagesize = 0; 3358c2ecf20Sopenharmony_ci} 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci/* 3388c2ecf20Sopenharmony_ci * img_update_free: Frees the buffer allocated for storing BIOS image 3398c2ecf20Sopenharmony_ci * Always called with lock held and returned with lock held 3408c2ecf20Sopenharmony_ci */ 3418c2ecf20Sopenharmony_cistatic void img_update_free(void) 3428c2ecf20Sopenharmony_ci{ 3438c2ecf20Sopenharmony_ci if (!rbu_data.image_update_buffer) 3448c2ecf20Sopenharmony_ci return; 3458c2ecf20Sopenharmony_ci /* 3468c2ecf20Sopenharmony_ci * zero out this buffer before freeing it to get rid of any stale 3478c2ecf20Sopenharmony_ci * BIOS image copied in memory. 3488c2ecf20Sopenharmony_ci */ 3498c2ecf20Sopenharmony_ci memset(rbu_data.image_update_buffer, 0, 3508c2ecf20Sopenharmony_ci rbu_data.image_update_buffer_size); 3518c2ecf20Sopenharmony_ci free_pages((unsigned long) rbu_data.image_update_buffer, 3528c2ecf20Sopenharmony_ci rbu_data.image_update_ordernum); 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci /* 3558c2ecf20Sopenharmony_ci * Re-initialize the rbu_data variables after a free 3568c2ecf20Sopenharmony_ci */ 3578c2ecf20Sopenharmony_ci rbu_data.image_update_ordernum = -1; 3588c2ecf20Sopenharmony_ci rbu_data.image_update_buffer = NULL; 3598c2ecf20Sopenharmony_ci rbu_data.image_update_buffer_size = 0; 3608c2ecf20Sopenharmony_ci rbu_data.bios_image_size = 0; 3618c2ecf20Sopenharmony_ci} 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci/* 3648c2ecf20Sopenharmony_ci * img_update_realloc: This function allocates the contiguous pages to 3658c2ecf20Sopenharmony_ci * accommodate the requested size of data. The memory address and size 3668c2ecf20Sopenharmony_ci * values are stored globally and on every call to this function the new 3678c2ecf20Sopenharmony_ci * size is checked to see if more data is required than the existing size. 3688c2ecf20Sopenharmony_ci * If true the previous memory is freed and new allocation is done to 3698c2ecf20Sopenharmony_ci * accommodate the new size. If the incoming size is less then than the 3708c2ecf20Sopenharmony_ci * already allocated size, then that memory is reused. This function is 3718c2ecf20Sopenharmony_ci * called with lock held and returns with lock held. 3728c2ecf20Sopenharmony_ci */ 3738c2ecf20Sopenharmony_cistatic int img_update_realloc(unsigned long size) 3748c2ecf20Sopenharmony_ci{ 3758c2ecf20Sopenharmony_ci unsigned char *image_update_buffer = NULL; 3768c2ecf20Sopenharmony_ci unsigned long img_buf_phys_addr; 3778c2ecf20Sopenharmony_ci int ordernum; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci /* 3808c2ecf20Sopenharmony_ci * check if the buffer of sufficient size has been 3818c2ecf20Sopenharmony_ci * already allocated 3828c2ecf20Sopenharmony_ci */ 3838c2ecf20Sopenharmony_ci if (rbu_data.image_update_buffer_size >= size) { 3848c2ecf20Sopenharmony_ci /* 3858c2ecf20Sopenharmony_ci * check for corruption 3868c2ecf20Sopenharmony_ci */ 3878c2ecf20Sopenharmony_ci if ((size != 0) && (rbu_data.image_update_buffer == NULL)) { 3888c2ecf20Sopenharmony_ci pr_err("corruption check failed\n"); 3898c2ecf20Sopenharmony_ci return -EINVAL; 3908c2ecf20Sopenharmony_ci } 3918c2ecf20Sopenharmony_ci /* 3928c2ecf20Sopenharmony_ci * we have a valid pre-allocated buffer with 3938c2ecf20Sopenharmony_ci * sufficient size 3948c2ecf20Sopenharmony_ci */ 3958c2ecf20Sopenharmony_ci return 0; 3968c2ecf20Sopenharmony_ci } 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci /* 3998c2ecf20Sopenharmony_ci * free any previously allocated buffer 4008c2ecf20Sopenharmony_ci */ 4018c2ecf20Sopenharmony_ci img_update_free(); 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci ordernum = get_order(size); 4068c2ecf20Sopenharmony_ci image_update_buffer = 4078c2ecf20Sopenharmony_ci (unsigned char *)__get_free_pages(GFP_DMA32, ordernum); 4088c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 4098c2ecf20Sopenharmony_ci if (!image_update_buffer) { 4108c2ecf20Sopenharmony_ci pr_debug("Not enough memory for image update: size = %ld\n", size); 4118c2ecf20Sopenharmony_ci return -ENOMEM; 4128c2ecf20Sopenharmony_ci } 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci img_buf_phys_addr = (unsigned long)virt_to_phys(image_update_buffer); 4158c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(img_buf_phys_addr > BIOS_SCAN_LIMIT)) 4168c2ecf20Sopenharmony_ci return -EINVAL; /* can't happen per definition */ 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci rbu_data.image_update_buffer = image_update_buffer; 4198c2ecf20Sopenharmony_ci rbu_data.image_update_buffer_size = size; 4208c2ecf20Sopenharmony_ci rbu_data.bios_image_size = rbu_data.image_update_buffer_size; 4218c2ecf20Sopenharmony_ci rbu_data.image_update_ordernum = ordernum; 4228c2ecf20Sopenharmony_ci return 0; 4238c2ecf20Sopenharmony_ci} 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_cistatic ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) 4268c2ecf20Sopenharmony_ci{ 4278c2ecf20Sopenharmony_ci int retval; 4288c2ecf20Sopenharmony_ci size_t bytes_left; 4298c2ecf20Sopenharmony_ci size_t data_length; 4308c2ecf20Sopenharmony_ci char *ptempBuf = buffer; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci /* check to see if we have something to return */ 4338c2ecf20Sopenharmony_ci if (rbu_data.num_packets == 0) { 4348c2ecf20Sopenharmony_ci pr_debug("no packets written\n"); 4358c2ecf20Sopenharmony_ci retval = -ENOMEM; 4368c2ecf20Sopenharmony_ci goto read_rbu_data_exit; 4378c2ecf20Sopenharmony_ci } 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci if (pos > rbu_data.imagesize) { 4408c2ecf20Sopenharmony_ci retval = 0; 4418c2ecf20Sopenharmony_ci pr_warn("data underrun\n"); 4428c2ecf20Sopenharmony_ci goto read_rbu_data_exit; 4438c2ecf20Sopenharmony_ci } 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci bytes_left = rbu_data.imagesize - pos; 4468c2ecf20Sopenharmony_ci data_length = min(bytes_left, count); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) 4498c2ecf20Sopenharmony_ci goto read_rbu_data_exit; 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci if ((pos + count) > rbu_data.imagesize) { 4528c2ecf20Sopenharmony_ci rbu_data.packet_read_count = 0; 4538c2ecf20Sopenharmony_ci /* this was the last copy */ 4548c2ecf20Sopenharmony_ci retval = bytes_left; 4558c2ecf20Sopenharmony_ci } else 4568c2ecf20Sopenharmony_ci retval = count; 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci read_rbu_data_exit: 4598c2ecf20Sopenharmony_ci return retval; 4608c2ecf20Sopenharmony_ci} 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_cistatic ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) 4638c2ecf20Sopenharmony_ci{ 4648c2ecf20Sopenharmony_ci /* check to see if we have something to return */ 4658c2ecf20Sopenharmony_ci if ((rbu_data.image_update_buffer == NULL) || 4668c2ecf20Sopenharmony_ci (rbu_data.bios_image_size == 0)) { 4678c2ecf20Sopenharmony_ci pr_debug("image_update_buffer %p, bios_image_size %lu\n", 4688c2ecf20Sopenharmony_ci rbu_data.image_update_buffer, 4698c2ecf20Sopenharmony_ci rbu_data.bios_image_size); 4708c2ecf20Sopenharmony_ci return -ENOMEM; 4718c2ecf20Sopenharmony_ci } 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci return memory_read_from_buffer(buffer, count, &pos, 4748c2ecf20Sopenharmony_ci rbu_data.image_update_buffer, rbu_data.bios_image_size); 4758c2ecf20Sopenharmony_ci} 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_cistatic ssize_t data_read(struct file *filp, struct kobject *kobj, 4788c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, 4798c2ecf20Sopenharmony_ci char *buffer, loff_t pos, size_t count) 4808c2ecf20Sopenharmony_ci{ 4818c2ecf20Sopenharmony_ci ssize_t ret_count = 0; 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci if (!strcmp(image_type, "mono")) 4868c2ecf20Sopenharmony_ci ret_count = read_rbu_mono_data(buffer, pos, count); 4878c2ecf20Sopenharmony_ci else if (!strcmp(image_type, "packet")) 4888c2ecf20Sopenharmony_ci ret_count = read_packet_data(buffer, pos, count); 4898c2ecf20Sopenharmony_ci else 4908c2ecf20Sopenharmony_ci pr_debug("invalid image type specified\n"); 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 4938c2ecf20Sopenharmony_ci return ret_count; 4948c2ecf20Sopenharmony_ci} 4958c2ecf20Sopenharmony_cistatic BIN_ATTR_RO(data, 0); 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_cistatic void callbackfn_rbu(const struct firmware *fw, void *context) 4988c2ecf20Sopenharmony_ci{ 4998c2ecf20Sopenharmony_ci rbu_data.entry_created = 0; 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci if (!fw) 5028c2ecf20Sopenharmony_ci return; 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci if (!fw->size) 5058c2ecf20Sopenharmony_ci goto out; 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 5088c2ecf20Sopenharmony_ci if (!strcmp(image_type, "mono")) { 5098c2ecf20Sopenharmony_ci if (!img_update_realloc(fw->size)) 5108c2ecf20Sopenharmony_ci memcpy(rbu_data.image_update_buffer, 5118c2ecf20Sopenharmony_ci fw->data, fw->size); 5128c2ecf20Sopenharmony_ci } else if (!strcmp(image_type, "packet")) { 5138c2ecf20Sopenharmony_ci /* 5148c2ecf20Sopenharmony_ci * we need to free previous packets if a 5158c2ecf20Sopenharmony_ci * new hunk of packets needs to be downloaded 5168c2ecf20Sopenharmony_ci */ 5178c2ecf20Sopenharmony_ci packet_empty_list(); 5188c2ecf20Sopenharmony_ci if (packetize_data(fw->data, fw->size)) 5198c2ecf20Sopenharmony_ci /* Incase something goes wrong when we are 5208c2ecf20Sopenharmony_ci * in middle of packetizing the data, we 5218c2ecf20Sopenharmony_ci * need to free up whatever packets might 5228c2ecf20Sopenharmony_ci * have been created before we quit. 5238c2ecf20Sopenharmony_ci */ 5248c2ecf20Sopenharmony_ci packet_empty_list(); 5258c2ecf20Sopenharmony_ci } else 5268c2ecf20Sopenharmony_ci pr_debug("invalid image type specified\n"); 5278c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 5288c2ecf20Sopenharmony_ci out: 5298c2ecf20Sopenharmony_ci release_firmware(fw); 5308c2ecf20Sopenharmony_ci} 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_cistatic ssize_t image_type_read(struct file *filp, struct kobject *kobj, 5338c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, 5348c2ecf20Sopenharmony_ci char *buffer, loff_t pos, size_t count) 5358c2ecf20Sopenharmony_ci{ 5368c2ecf20Sopenharmony_ci int size = 0; 5378c2ecf20Sopenharmony_ci if (!pos) 5388c2ecf20Sopenharmony_ci size = scnprintf(buffer, count, "%s\n", image_type); 5398c2ecf20Sopenharmony_ci return size; 5408c2ecf20Sopenharmony_ci} 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_cistatic ssize_t image_type_write(struct file *filp, struct kobject *kobj, 5438c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, 5448c2ecf20Sopenharmony_ci char *buffer, loff_t pos, size_t count) 5458c2ecf20Sopenharmony_ci{ 5468c2ecf20Sopenharmony_ci int rc = count; 5478c2ecf20Sopenharmony_ci int req_firm_rc = 0; 5488c2ecf20Sopenharmony_ci int i; 5498c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 5508c2ecf20Sopenharmony_ci /* 5518c2ecf20Sopenharmony_ci * Find the first newline or space 5528c2ecf20Sopenharmony_ci */ 5538c2ecf20Sopenharmony_ci for (i = 0; i < count; ++i) 5548c2ecf20Sopenharmony_ci if (buffer[i] == '\n' || buffer[i] == ' ') { 5558c2ecf20Sopenharmony_ci buffer[i] = '\0'; 5568c2ecf20Sopenharmony_ci break; 5578c2ecf20Sopenharmony_ci } 5588c2ecf20Sopenharmony_ci if (i == count) 5598c2ecf20Sopenharmony_ci buffer[count] = '\0'; 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci if (strstr(buffer, "mono")) 5628c2ecf20Sopenharmony_ci strcpy(image_type, "mono"); 5638c2ecf20Sopenharmony_ci else if (strstr(buffer, "packet")) 5648c2ecf20Sopenharmony_ci strcpy(image_type, "packet"); 5658c2ecf20Sopenharmony_ci else if (strstr(buffer, "init")) { 5668c2ecf20Sopenharmony_ci /* 5678c2ecf20Sopenharmony_ci * If due to the user error the driver gets in a bad 5688c2ecf20Sopenharmony_ci * state where even though it is loaded , the 5698c2ecf20Sopenharmony_ci * /sys/class/firmware/dell_rbu entries are missing. 5708c2ecf20Sopenharmony_ci * to cover this situation the user can recreate entries 5718c2ecf20Sopenharmony_ci * by writing init to image_type. 5728c2ecf20Sopenharmony_ci */ 5738c2ecf20Sopenharmony_ci if (!rbu_data.entry_created) { 5748c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 5758c2ecf20Sopenharmony_ci req_firm_rc = request_firmware_nowait(THIS_MODULE, 5768c2ecf20Sopenharmony_ci FW_ACTION_NOHOTPLUG, "dell_rbu", 5778c2ecf20Sopenharmony_ci &rbu_device->dev, GFP_KERNEL, &context, 5788c2ecf20Sopenharmony_ci callbackfn_rbu); 5798c2ecf20Sopenharmony_ci if (req_firm_rc) { 5808c2ecf20Sopenharmony_ci pr_err("request_firmware_nowait failed %d\n", rc); 5818c2ecf20Sopenharmony_ci rc = -EIO; 5828c2ecf20Sopenharmony_ci } else 5838c2ecf20Sopenharmony_ci rbu_data.entry_created = 1; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 5868c2ecf20Sopenharmony_ci } 5878c2ecf20Sopenharmony_ci } else { 5888c2ecf20Sopenharmony_ci pr_warn("image_type is invalid\n"); 5898c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 5908c2ecf20Sopenharmony_ci return -EINVAL; 5918c2ecf20Sopenharmony_ci } 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci /* we must free all previous allocations */ 5948c2ecf20Sopenharmony_ci packet_empty_list(); 5958c2ecf20Sopenharmony_ci img_update_free(); 5968c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci return rc; 5998c2ecf20Sopenharmony_ci} 6008c2ecf20Sopenharmony_cistatic BIN_ATTR_RW(image_type, 0); 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_cistatic ssize_t packet_size_read(struct file *filp, struct kobject *kobj, 6038c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, 6048c2ecf20Sopenharmony_ci char *buffer, loff_t pos, size_t count) 6058c2ecf20Sopenharmony_ci{ 6068c2ecf20Sopenharmony_ci int size = 0; 6078c2ecf20Sopenharmony_ci if (!pos) { 6088c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 6098c2ecf20Sopenharmony_ci size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize); 6108c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 6118c2ecf20Sopenharmony_ci } 6128c2ecf20Sopenharmony_ci return size; 6138c2ecf20Sopenharmony_ci} 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_cistatic ssize_t packet_size_write(struct file *filp, struct kobject *kobj, 6168c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, 6178c2ecf20Sopenharmony_ci char *buffer, loff_t pos, size_t count) 6188c2ecf20Sopenharmony_ci{ 6198c2ecf20Sopenharmony_ci unsigned long temp; 6208c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 6218c2ecf20Sopenharmony_ci packet_empty_list(); 6228c2ecf20Sopenharmony_ci sscanf(buffer, "%lu", &temp); 6238c2ecf20Sopenharmony_ci if (temp < 0xffffffff) 6248c2ecf20Sopenharmony_ci rbu_data.packetsize = temp; 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 6278c2ecf20Sopenharmony_ci return count; 6288c2ecf20Sopenharmony_ci} 6298c2ecf20Sopenharmony_cistatic BIN_ATTR_RW(packet_size, 0); 6308c2ecf20Sopenharmony_ci 6318c2ecf20Sopenharmony_cistatic struct bin_attribute *rbu_bin_attrs[] = { 6328c2ecf20Sopenharmony_ci &bin_attr_data, 6338c2ecf20Sopenharmony_ci &bin_attr_image_type, 6348c2ecf20Sopenharmony_ci &bin_attr_packet_size, 6358c2ecf20Sopenharmony_ci NULL 6368c2ecf20Sopenharmony_ci}; 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_cistatic const struct attribute_group rbu_group = { 6398c2ecf20Sopenharmony_ci .bin_attrs = rbu_bin_attrs, 6408c2ecf20Sopenharmony_ci}; 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_cistatic int __init dcdrbu_init(void) 6438c2ecf20Sopenharmony_ci{ 6448c2ecf20Sopenharmony_ci int rc; 6458c2ecf20Sopenharmony_ci spin_lock_init(&rbu_data.lock); 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci init_packet_head(); 6488c2ecf20Sopenharmony_ci rbu_device = platform_device_register_simple("dell_rbu", -1, NULL, 0); 6498c2ecf20Sopenharmony_ci if (IS_ERR(rbu_device)) { 6508c2ecf20Sopenharmony_ci pr_err("platform_device_register_simple failed\n"); 6518c2ecf20Sopenharmony_ci return PTR_ERR(rbu_device); 6528c2ecf20Sopenharmony_ci } 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci rc = sysfs_create_group(&rbu_device->dev.kobj, &rbu_group); 6558c2ecf20Sopenharmony_ci if (rc) 6568c2ecf20Sopenharmony_ci goto out_devreg; 6578c2ecf20Sopenharmony_ci 6588c2ecf20Sopenharmony_ci rbu_data.entry_created = 0; 6598c2ecf20Sopenharmony_ci return 0; 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ciout_devreg: 6628c2ecf20Sopenharmony_ci platform_device_unregister(rbu_device); 6638c2ecf20Sopenharmony_ci return rc; 6648c2ecf20Sopenharmony_ci} 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_cistatic __exit void dcdrbu_exit(void) 6678c2ecf20Sopenharmony_ci{ 6688c2ecf20Sopenharmony_ci spin_lock(&rbu_data.lock); 6698c2ecf20Sopenharmony_ci packet_empty_list(); 6708c2ecf20Sopenharmony_ci img_update_free(); 6718c2ecf20Sopenharmony_ci spin_unlock(&rbu_data.lock); 6728c2ecf20Sopenharmony_ci sysfs_remove_group(&rbu_device->dev.kobj, &rbu_group); 6738c2ecf20Sopenharmony_ci platform_device_unregister(rbu_device); 6748c2ecf20Sopenharmony_ci} 6758c2ecf20Sopenharmony_ci 6768c2ecf20Sopenharmony_cimodule_exit(dcdrbu_exit); 6778c2ecf20Sopenharmony_cimodule_init(dcdrbu_init); 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci/* vim:noet:ts=8:sw=8 6808c2ecf20Sopenharmony_ci*/ 681