linux/drivers/staging/gdm72xx/usb_boot.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
   3 *
   4 * This software is licensed under the terms of the GNU General Public
   5 * License version 2, as published by the Free Software Foundation, and
   6 * may be copied, distributed, and modified under those terms.
   7 *
   8 * This program is distributed in the hope that it will be useful,
   9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11 * GNU General Public License for more details.
  12 */
  13
  14#include <linux/uaccess.h>
  15#include <linux/module.h>
  16#include <linux/kernel.h>
  17#include <linux/mm.h>
  18#include <linux/usb.h>
  19#include <linux/unistd.h>
  20#include <linux/slab.h>
  21#include <linux/firmware.h>
  22
  23#include <asm/byteorder.h>
  24#include "gdm_usb.h"
  25#include "usb_boot.h"
  26
  27#define DN_KERNEL_MAGIC_NUMBER  0x10760001
  28#define DN_ROOTFS_MAGIC_NUMBER  0x10760002
  29
  30#define DOWNLOAD_SIZE           1024
  31
  32#define MAX_IMG_CNT             16
  33#define FW_DIR                  "gdm72xx/"
  34#define FW_UIMG                 "gdmuimg.bin"
  35#define FW_KERN                 "zImage"
  36#define FW_FS                   "ramdisk.jffs2"
  37
  38struct dn_header {
  39        u32     magic_num;
  40        u32     file_size;
  41};
  42
  43struct img_header {
  44        u32     magic_code;
  45        u32     count;
  46        u32     len;
  47        u32     offset[MAX_IMG_CNT];
  48        char    hostname[32];
  49        char    date[32];
  50};
  51
  52struct fw_info {
  53        u32     id;
  54        u32     len;
  55        u32     kernel_len;
  56        u32     rootfs_len;
  57        u32     kernel_offset;
  58        u32     rootfs_offset;
  59        u32     fw_ver;
  60        u32     mac_ver;
  61        char    hostname[32];
  62        char    userid[16];
  63        char    date[32];
  64        char    user_desc[128];
  65};
  66
  67static void array_le32_to_cpu(u32 *arr, int num)
  68{
  69        int i;
  70        for (i = 0; i < num; i++, arr++)
  71                *arr = __le32_to_cpu(*arr);
  72}
  73
  74static u8 *tx_buf;
  75
  76static int gdm_wibro_send(struct usb_device *usbdev, void *data, int len)
  77{
  78        int ret;
  79        int actual;
  80
  81        ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), data, len,
  82                        &actual, 1000);
  83
  84        if (ret < 0) {
  85                dev_err(&usbdev->dev, "Error : usb_bulk_msg ( result = %d )\n",
  86                        ret);
  87                return ret;
  88        }
  89        return 0;
  90}
  91
  92static int gdm_wibro_recv(struct usb_device *usbdev, void *data, int len)
  93{
  94        int ret;
  95        int actual;
  96
  97        ret = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), data, len,
  98                        &actual, 5000);
  99
 100        if (ret < 0) {
 101                dev_err(&usbdev->dev,
 102                        "Error : usb_bulk_msg(recv) ( result = %d )\n", ret);
 103                return ret;
 104        }
 105        return 0;
 106}
 107
 108static int download_image(struct usb_device *usbdev,
 109                                const struct firmware *firm,
 110                                loff_t pos, u32 img_len, u32 magic_num)
 111{
 112        struct dn_header h;
 113        int ret = 0;
 114        u32 size;
 115
 116        size = ALIGN(img_len, DOWNLOAD_SIZE);
 117        h.magic_num = __cpu_to_be32(magic_num);
 118        h.file_size = __cpu_to_be32(size);
 119
 120        ret = gdm_wibro_send(usbdev, &h, sizeof(h));
 121        if (ret < 0)
 122                return ret;
 123
 124        while (img_len > 0) {
 125                if (img_len > DOWNLOAD_SIZE)
 126                        size = DOWNLOAD_SIZE;
 127                else
 128                        size = img_len; /* the last chunk of data */
 129
 130                memcpy(tx_buf, firm->data + pos, size);
 131                ret = gdm_wibro_send(usbdev, tx_buf, size);
 132
 133                if (ret < 0)
 134                        return ret;
 135
 136                img_len -= size;
 137                pos += size;
 138        }
 139
 140        return ret;
 141}
 142
 143int usb_boot(struct usb_device *usbdev, u16 pid)
 144{
 145        int i, ret = 0;
 146        struct img_header hdr;
 147        struct fw_info fw_info;
 148        loff_t pos = 0;
 149        char *img_name = FW_DIR FW_UIMG;
 150        const struct firmware *firm;
 151
 152        ret = request_firmware(&firm, img_name, &usbdev->dev);
 153        if (ret < 0) {
 154                dev_err(&usbdev->dev,
 155                        "requesting firmware %s failed with error %d\n",
 156                        img_name, ret);
 157                return ret;
 158        }
 159
 160        tx_buf = kmalloc(DOWNLOAD_SIZE, GFP_KERNEL);
 161        if (tx_buf == NULL)
 162                return -ENOMEM;
 163
 164        if (firm->size < sizeof(hdr)) {
 165                dev_err(&usbdev->dev, "Cannot read the image info.\n");
 166                ret = -EIO;
 167                goto out;
 168        }
 169        memcpy(&hdr, firm->data, sizeof(hdr));
 170
 171        array_le32_to_cpu((u32 *)&hdr, 19);
 172#if 0
 173        if (hdr.magic_code != 0x10767fff) {
 174                dev_err(&usbdev->dev, "Invalid magic code 0x%08x\n",
 175                        hdr.magic_code);
 176                ret = -EINVAL;
 177                goto out;
 178        }
 179#endif
 180        if (hdr.count > MAX_IMG_CNT) {
 181                dev_err(&usbdev->dev, "Too many images. %d\n", hdr.count);
 182                ret = -EINVAL;
 183                goto out;
 184        }
 185
 186        for (i = 0; i < hdr.count; i++) {
 187                if (hdr.offset[i] > hdr.len) {
 188                        dev_err(&usbdev->dev,
 189                                "Invalid offset. Entry = %d Offset = 0x%08x Image length = 0x%08x\n",
 190                                i, hdr.offset[i], hdr.len);
 191                        ret = -EINVAL;
 192                        goto out;
 193                }
 194
 195                pos = hdr.offset[i];
 196                if (firm->size < sizeof(fw_info) + pos) {
 197                        dev_err(&usbdev->dev, "Cannot read the FW info.\n");
 198                        ret = -EIO;
 199                        goto out;
 200                }
 201                memcpy(&fw_info, firm->data + pos, sizeof(fw_info));
 202
 203                array_le32_to_cpu((u32 *)&fw_info, 8);
 204#if 0
 205                if ((fw_info.id & 0xfffff000) != 0x10767000) {
 206                        dev_err(&usbdev->dev, "Invalid FW id. 0x%08x\n",
 207                                fw_info.id);
 208                        ret = -EIO;
 209                        goto out;
 210                }
 211#endif
 212
 213                if ((fw_info.id & 0xffff) != pid)
 214                        continue;
 215
 216                pos = hdr.offset[i] + fw_info.kernel_offset;
 217                if (firm->size < fw_info.kernel_len + pos) {
 218                        dev_err(&usbdev->dev, "Kernel FW is too small.\n");
 219                        goto out;
 220                }
 221
 222                ret = download_image(usbdev, firm, pos,
 223                                fw_info.kernel_len, DN_KERNEL_MAGIC_NUMBER);
 224                if (ret < 0)
 225                        goto out;
 226                dev_info(&usbdev->dev, "GCT: Kernel download success.\n");
 227
 228                pos = hdr.offset[i] + fw_info.rootfs_offset;
 229                if (firm->size < fw_info.rootfs_len + pos) {
 230                        dev_err(&usbdev->dev, "Filesystem FW is too small.\n");
 231                        goto out;
 232                }
 233                ret = download_image(usbdev, firm, pos, fw_info.rootfs_len,
 234                                DN_ROOTFS_MAGIC_NUMBER);
 235                if (ret < 0)
 236                        goto out;
 237                dev_info(&usbdev->dev, "GCT: Filesystem download success.\n");
 238
 239                break;
 240        }
 241
 242        if (i == hdr.count) {
 243                dev_err(&usbdev->dev, "Firmware for gsk%x is not installed.\n",
 244                        pid);
 245                ret = -EINVAL;
 246        }
 247out:
 248        release_firmware(firm);
 249        kfree(tx_buf);
 250        return ret;
 251}
 252
 253/*#define GDM7205_PADDING               256 */
 254#define DOWNLOAD_CHUCK                  2048
 255#define KERNEL_TYPE_STRING              "linux"
 256#define FS_TYPE_STRING                  "rootfs"
 257
 258static int em_wait_ack(struct usb_device *usbdev, int send_zlp)
 259{
 260        int ack;
 261        int ret = -1;
 262
 263        if (send_zlp) {
 264                /*Send ZLP*/
 265                ret = gdm_wibro_send(usbdev, NULL, 0);
 266                if (ret < 0)
 267                        goto out;
 268        }
 269
 270        /*Wait for ACK*/
 271        ret = gdm_wibro_recv(usbdev, &ack, sizeof(ack));
 272        if (ret < 0)
 273                goto out;
 274out:
 275        return ret;
 276}
 277
 278static int em_download_image(struct usb_device *usbdev, const char *img_name,
 279                                char *type_string)
 280{
 281        char *buf = NULL;
 282        loff_t pos = 0;
 283        int ret = 0;
 284        int len;
 285        int img_len;
 286        const struct firmware *firm;
 287        #if defined(GDM7205_PADDING)
 288        const int pad_size = GDM7205_PADDING;
 289        #else
 290        const int pad_size = 0;
 291        #endif
 292
 293        ret = request_firmware(&firm, img_name, &usbdev->dev);
 294        if (ret < 0) {
 295                dev_err(&usbdev->dev,
 296                        "requesting firmware %s failed with error %d\n",
 297                        img_name, ret);
 298                return ret;
 299        }
 300
 301        buf = kmalloc(DOWNLOAD_CHUCK + pad_size, GFP_KERNEL);
 302        if (buf == NULL)
 303                return -ENOMEM;
 304
 305        strcpy(buf+pad_size, type_string);
 306        ret = gdm_wibro_send(usbdev, buf, strlen(type_string)+pad_size);
 307        if (ret < 0)
 308                goto out;
 309
 310        img_len = firm->size;
 311
 312        if (img_len <= 0) {
 313                ret = -1;
 314                goto out;
 315        }
 316
 317        while (img_len > 0) {
 318                if (img_len > DOWNLOAD_CHUCK)
 319                        len = DOWNLOAD_CHUCK;
 320                else
 321                        len = img_len; /* the last chunk of data */
 322
 323                memcpy(buf+pad_size, firm->data + pos, len);
 324                ret = gdm_wibro_send(usbdev, buf, len+pad_size);
 325
 326                if (ret < 0)
 327                        goto out;
 328
 329                img_len -= DOWNLOAD_CHUCK;
 330                pos += DOWNLOAD_CHUCK;
 331
 332                ret = em_wait_ack(usbdev, ((len+pad_size) % 512 == 0));
 333                if (ret < 0)
 334                        goto out;
 335        }
 336
 337        ret = em_wait_ack(usbdev, 1);
 338        if (ret < 0)
 339                goto out;
 340
 341out:
 342        release_firmware(firm);
 343        kfree(buf);
 344
 345        return ret;
 346}
 347
 348static int em_fw_reset(struct usb_device *usbdev)
 349{
 350        int ret;
 351
 352        /*Send ZLP*/
 353        ret = gdm_wibro_send(usbdev, NULL, 0);
 354        return ret;
 355}
 356
 357int usb_emergency(struct usb_device *usbdev)
 358{
 359        int ret;
 360        const char *kern_name = FW_DIR FW_KERN;
 361        const char *fs_name = FW_DIR FW_FS;
 362
 363        ret = em_download_image(usbdev, kern_name, KERNEL_TYPE_STRING);
 364        if (ret < 0)
 365                return ret;
 366        dev_err(&usbdev->dev, "GCT Emergency: Kernel download success.\n");
 367
 368        ret = em_download_image(usbdev, fs_name, FS_TYPE_STRING);
 369        if (ret < 0)
 370                return ret;
 371        dev_info(&usbdev->dev, "GCT Emergency: Filesystem download success.\n");
 372
 373        ret = em_fw_reset(usbdev);
 374
 375        return ret;
 376}
 377