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        __be32  magic_num;
  40        __be32  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
  71        for (i = 0; i < num; i++, arr++)
  72                le32_to_cpus(arr);
  73}
  74
  75static u8 *tx_buf;
  76
  77static int gdm_wibro_send(struct usb_device *usbdev, void *data, int len)
  78{
  79        int ret;
  80        int actual;
  81
  82        ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), data, len,
  83                           &actual, 1000);
  84
  85        if (ret < 0) {
  86                dev_err(&usbdev->dev, "Error : usb_bulk_msg ( result = %d )\n",
  87                        ret);
  88                return ret;
  89        }
  90        return 0;
  91}
  92
  93static int gdm_wibro_recv(struct usb_device *usbdev, void *data, int len)
  94{
  95        int ret;
  96        int actual;
  97
  98        ret = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), data, len,
  99                           &actual, 5000);
 100
 101        if (ret < 0) {
 102                dev_err(&usbdev->dev,
 103                        "Error : usb_bulk_msg(recv) ( result = %d )\n", ret);
 104                return ret;
 105        }
 106        return 0;
 107}
 108
 109static int download_image(struct usb_device *usbdev,
 110                          const struct firmware *firm,
 111                          loff_t pos, u32 img_len, u32 magic_num)
 112{
 113        struct dn_header h;
 114        int ret = 0;
 115        u32 size;
 116
 117        size = ALIGN(img_len, DOWNLOAD_SIZE);
 118        h.magic_num = cpu_to_be32(magic_num);
 119        h.file_size = cpu_to_be32(size);
 120
 121        ret = gdm_wibro_send(usbdev, &h, sizeof(h));
 122        if (ret < 0)
 123                return ret;
 124
 125        while (img_len > 0) {
 126                if (img_len > DOWNLOAD_SIZE)
 127                        size = DOWNLOAD_SIZE;
 128                else
 129                        size = img_len; /* the last chunk of data */
 130
 131                memcpy(tx_buf, firm->data + pos, size);
 132                ret = gdm_wibro_send(usbdev, tx_buf, size);
 133
 134                if (ret < 0)
 135                        return ret;
 136
 137                img_len -= size;
 138                pos += size;
 139        }
 140
 141        return ret;
 142}
 143
 144int usb_boot(struct usb_device *usbdev, u16 pid)
 145{
 146        int i, ret = 0;
 147        struct img_header hdr;
 148        struct fw_info fw_info;
 149        loff_t pos = 0;
 150        char *img_name = FW_DIR FW_UIMG;
 151        const struct firmware *firm;
 152
 153        ret = request_firmware(&firm, img_name, &usbdev->dev);
 154        if (ret < 0) {
 155                dev_err(&usbdev->dev,
 156                        "requesting firmware %s failed with error %d\n",
 157                        img_name, ret);
 158                return ret;
 159        }
 160
 161        tx_buf = kmalloc(DOWNLOAD_SIZE, GFP_KERNEL);
 162        if (tx_buf == NULL)
 163                return -ENOMEM;
 164
 165        if (firm->size < sizeof(hdr)) {
 166                dev_err(&usbdev->dev, "Cannot read the image info.\n");
 167                ret = -EIO;
 168                goto out;
 169        }
 170        memcpy(&hdr, firm->data, sizeof(hdr));
 171
 172        array_le32_to_cpu((u32 *)&hdr, 19);
 173
 174        if (hdr.count > MAX_IMG_CNT) {
 175                dev_err(&usbdev->dev, "Too many images. %d\n", hdr.count);
 176                ret = -EINVAL;
 177                goto out;
 178        }
 179
 180        for (i = 0; i < hdr.count; i++) {
 181                if (hdr.offset[i] > hdr.len) {
 182                        dev_err(&usbdev->dev,
 183                                "Invalid offset. Entry = %d Offset = 0x%08x Image length = 0x%08x\n",
 184                                i, hdr.offset[i], hdr.len);
 185                        ret = -EINVAL;
 186                        goto out;
 187                }
 188
 189                pos = hdr.offset[i];
 190                if (firm->size < sizeof(fw_info) + pos) {
 191                        dev_err(&usbdev->dev, "Cannot read the FW info.\n");
 192                        ret = -EIO;
 193                        goto out;
 194                }
 195                memcpy(&fw_info, firm->data + pos, sizeof(fw_info));
 196
 197                array_le32_to_cpu((u32 *)&fw_info, 8);
 198
 199                if ((fw_info.id & 0xffff) != pid)
 200                        continue;
 201
 202                pos = hdr.offset[i] + fw_info.kernel_offset;
 203                if (firm->size < fw_info.kernel_len + pos) {
 204                        dev_err(&usbdev->dev, "Kernel FW is too small.\n");
 205                        goto out;
 206                }
 207
 208                ret = download_image(usbdev, firm, pos, fw_info.kernel_len,
 209                                     DN_KERNEL_MAGIC_NUMBER);
 210                if (ret < 0)
 211                        goto out;
 212                dev_info(&usbdev->dev, "GCT: Kernel download success.\n");
 213
 214                pos = hdr.offset[i] + fw_info.rootfs_offset;
 215                if (firm->size < fw_info.rootfs_len + pos) {
 216                        dev_err(&usbdev->dev, "Filesystem FW is too small.\n");
 217                        goto out;
 218                }
 219                ret = download_image(usbdev, firm, pos, fw_info.rootfs_len,
 220                                     DN_ROOTFS_MAGIC_NUMBER);
 221                if (ret < 0)
 222                        goto out;
 223                dev_info(&usbdev->dev, "GCT: Filesystem download success.\n");
 224
 225                break;
 226        }
 227
 228        if (i == hdr.count) {
 229                dev_err(&usbdev->dev, "Firmware for gsk%x is not installed.\n",
 230                        pid);
 231                ret = -EINVAL;
 232        }
 233out:
 234        release_firmware(firm);
 235        kfree(tx_buf);
 236        return ret;
 237}
 238
 239/*#define GDM7205_PADDING               256 */
 240#define DOWNLOAD_CHUCK                  2048
 241#define KERNEL_TYPE_STRING              "linux"
 242#define FS_TYPE_STRING                  "rootfs"
 243
 244static int em_wait_ack(struct usb_device *usbdev, int send_zlp)
 245{
 246        int ack;
 247        int ret = -1;
 248
 249        if (send_zlp) {
 250                /*Send ZLP*/
 251                ret = gdm_wibro_send(usbdev, NULL, 0);
 252                if (ret < 0)
 253                        goto out;
 254        }
 255
 256        /*Wait for ACK*/
 257        ret = gdm_wibro_recv(usbdev, &ack, sizeof(ack));
 258        if (ret < 0)
 259                goto out;
 260out:
 261        return ret;
 262}
 263
 264static int em_download_image(struct usb_device *usbdev, const char *img_name,
 265                             char *type_string)
 266{
 267        char *buf = NULL;
 268        loff_t pos = 0;
 269        int ret = 0;
 270        int len;
 271        int img_len;
 272        const struct firmware *firm;
 273        #if defined(GDM7205_PADDING)
 274        const int pad_size = GDM7205_PADDING;
 275        #else
 276        const int pad_size = 0;
 277        #endif
 278
 279        ret = request_firmware(&firm, img_name, &usbdev->dev);
 280        if (ret < 0) {
 281                dev_err(&usbdev->dev,
 282                        "requesting firmware %s failed with error %d\n",
 283                        img_name, ret);
 284                return ret;
 285        }
 286
 287        buf = kmalloc(DOWNLOAD_CHUCK + pad_size, GFP_KERNEL);
 288        if (buf == NULL)
 289                return -ENOMEM;
 290
 291        strcpy(buf+pad_size, type_string);
 292        ret = gdm_wibro_send(usbdev, buf, strlen(type_string)+pad_size);
 293        if (ret < 0)
 294                goto out;
 295
 296        img_len = firm->size;
 297
 298        if (img_len <= 0) {
 299                ret = -1;
 300                goto out;
 301        }
 302
 303        while (img_len > 0) {
 304                if (img_len > DOWNLOAD_CHUCK)
 305                        len = DOWNLOAD_CHUCK;
 306                else
 307                        len = img_len; /* the last chunk of data */
 308
 309                memcpy(buf+pad_size, firm->data + pos, len);
 310                ret = gdm_wibro_send(usbdev, buf, len+pad_size);
 311
 312                if (ret < 0)
 313                        goto out;
 314
 315                img_len -= DOWNLOAD_CHUCK;
 316                pos += DOWNLOAD_CHUCK;
 317
 318                ret = em_wait_ack(usbdev, ((len+pad_size) % 512 == 0));
 319                if (ret < 0)
 320                        goto out;
 321        }
 322
 323        ret = em_wait_ack(usbdev, 1);
 324        if (ret < 0)
 325                goto out;
 326
 327out:
 328        release_firmware(firm);
 329        kfree(buf);
 330
 331        return ret;
 332}
 333
 334static int em_fw_reset(struct usb_device *usbdev)
 335{
 336        /*Send ZLP*/
 337        return gdm_wibro_send(usbdev, NULL, 0);
 338}
 339
 340int usb_emergency(struct usb_device *usbdev)
 341{
 342        int ret;
 343        const char *kern_name = FW_DIR FW_KERN;
 344        const char *fs_name = FW_DIR FW_FS;
 345
 346        ret = em_download_image(usbdev, kern_name, KERNEL_TYPE_STRING);
 347        if (ret < 0)
 348                return ret;
 349        dev_err(&usbdev->dev, "GCT Emergency: Kernel download success.\n");
 350
 351        ret = em_download_image(usbdev, fs_name, FS_TYPE_STRING);
 352        if (ret < 0)
 353                return ret;
 354        dev_info(&usbdev->dev, "GCT Emergency: Filesystem download success.\n");
 355
 356        ret = em_fw_reset(usbdev);
 357
 358        return ret;
 359}
 360