linux-stable/drivers/staging/gdm72xx/usb_boot.c
Linus Torvalds a0e881b7c1 Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull second vfs pile from Al Viro:
 "The stuff in there: fsfreeze deadlock fixes by Jan (essentially, the
  deadlock reproduced by xfstests 068), symlink and hardlink restriction
  patches, plus assorted cleanups and fixes.

  Note that another fsfreeze deadlock (emergency thaw one) is *not*
  dealt with - the series by Fernando conflicts a lot with Jan's, breaks
  userland ABI (FIFREEZE semantics gets changed) and trades the deadlock
  for massive vfsmount leak; this is going to be handled next cycle.
  There probably will be another pull request, but that stuff won't be
  in it."

Fix up trivial conflicts due to unrelated changes next to each other in
drivers/{staging/gdm72xx/usb_boot.c, usb/gadget/storage_common.c}

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: (54 commits)
  delousing target_core_file a bit
  Documentation: Correct s_umount state for freeze_fs/unfreeze_fs
  fs: Remove old freezing mechanism
  ext2: Implement freezing
  btrfs: Convert to new freezing mechanism
  nilfs2: Convert to new freezing mechanism
  ntfs: Convert to new freezing mechanism
  fuse: Convert to new freezing mechanism
  gfs2: Convert to new freezing mechanism
  ocfs2: Convert to new freezing mechanism
  xfs: Convert to new freezing code
  ext4: Convert to new freezing mechanism
  fs: Protect write paths by sb_start_write - sb_end_write
  fs: Skip atime update on frozen filesystem
  fs: Add freezing handling to mnt_want_write() / mnt_drop_write()
  fs: Improve filesystem freezing handling
  switch the protection of percpu_counter list to spinlock
  nfsd: Push mnt_want_write() outside of i_mutex
  btrfs: Push mnt_want_write() outside of i_mutex
  fat: Push mnt_want_write() outside of i_mutex
  ...
2012-08-01 10:26:23 -07:00

399 lines
8 KiB
C

/*
* Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/usb.h>
#include <linux/unistd.h>
#include <linux/slab.h>
#include <asm/byteorder.h>
#include "gdm_usb.h"
#include "usb_boot.h"
#define DN_KERNEL_MAGIC_NUMBER 0x10760001
#define DN_ROOTFS_MAGIC_NUMBER 0x10760002
#define DOWNLOAD_SIZE 1024
#define DH2B(x) __cpu_to_be32(x)
#define DL2H(x) __le32_to_cpu(x)
#define MIN(a, b) ((a) > (b) ? (b) : (a))
#define MAX_IMG_CNT 16
#define UIMG_PATH "/lib/firmware/gdm72xx/gdmuimg.bin"
#define KERN_PATH "/lib/firmware/gdm72xx/zImage"
#define FS_PATH "/lib/firmware/gdm72xx/ramdisk.jffs2"
struct dn_header {
u32 magic_num;
u32 file_size;
};
struct img_header {
u32 magic_code;
u32 count;
u32 len;
u32 offset[MAX_IMG_CNT];
char hostname[32];
char date[32];
};
struct fw_info {
u32 id;
u32 len;
u32 kernel_len;
u32 rootfs_len;
u32 kernel_offset;
u32 rootfs_offset;
u32 fw_ver;
u32 mac_ver;
char hostname[32];
char userid[16];
char date[32];
char user_desc[128];
};
static void array_le32_to_cpu(u32 *arr, int num)
{
int i;
for (i = 0; i < num; i++, arr++)
*arr = DL2H(*arr);
}
static u8 *tx_buf;
static int gdm_wibro_send(struct usb_device *usbdev, void *data, int len)
{
int ret;
int actual;
ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), data, len,
&actual, 1000);
if (ret < 0) {
printk(KERN_ERR "Error : usb_bulk_msg ( result = %d )\n", ret);
return ret;
}
return 0;
}
static int gdm_wibro_recv(struct usb_device *usbdev, void *data, int len)
{
int ret;
int actual;
ret = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), data, len,
&actual, 5000);
if (ret < 0) {
printk(KERN_ERR "Error : usb_bulk_msg(recv) ( result = %d )\n",
ret);
return ret;
}
return 0;
}
static int download_image(struct usb_device *usbdev, struct file *filp,
loff_t *pos, u32 img_len, u32 magic_num)
{
struct dn_header h;
int ret = 0;
u32 size;
int len, readn;
size = (img_len + DOWNLOAD_SIZE - 1) & ~(DOWNLOAD_SIZE - 1);
h.magic_num = DH2B(magic_num);
h.file_size = DH2B(size);
ret = gdm_wibro_send(usbdev, &h, sizeof(h));
if (ret < 0)
goto out;
readn = 0;
while ((len = filp->f_op->read(filp, tx_buf, DOWNLOAD_SIZE, pos))) {
if (len < 0) {
ret = -1;
goto out;
}
readn += len;
ret = gdm_wibro_send(usbdev, tx_buf, DOWNLOAD_SIZE);
if (ret < 0)
goto out;
if (readn >= img_len)
break;
}
if (readn < img_len) {
printk(KERN_ERR "gdmwm: Cannot read to the requested size. "
"Read = %d Requested = %d\n", readn, img_len);
ret = -EIO;
}
out:
return ret;
}
int usb_boot(struct usb_device *usbdev, u16 pid)
{
int i, ret = 0;
struct file *filp = NULL;
struct inode *inode = NULL;
static mm_segment_t fs;
struct img_header hdr;
struct fw_info fw_info;
loff_t pos = 0;
char *img_name = UIMG_PATH;
int len;
tx_buf = kmalloc(DOWNLOAD_SIZE, GFP_KERNEL);
if (tx_buf == NULL) {
printk(KERN_ERR "Error: kmalloc\n");
return -ENOMEM;
}
fs = get_fs();
set_fs(get_ds());
filp = filp_open(img_name, O_RDONLY | O_LARGEFILE, 0);
if (IS_ERR(filp)) {
printk(KERN_ERR "Can't find %s.\n", img_name);
ret = PTR_ERR(filp);
goto restore_fs;
}
inode = filp->f_dentry->d_inode;
if (!S_ISREG(inode->i_mode)) {
printk(KERN_ERR "Invalid file type: %s\n", img_name);
ret = -EINVAL;
goto out;
}
len = filp->f_op->read(filp, (u8 *)&hdr, sizeof(hdr), &pos);
if (len != sizeof(hdr)) {
printk(KERN_ERR "gdmwm: Cannot read the image info.\n");
ret = -EIO;
goto out;
}
array_le32_to_cpu((u32 *)&hdr, 19);
#if 0
if (hdr.magic_code != 0x10767fff) {
printk(KERN_ERR "gdmwm: Invalid magic code 0x%08x\n",
hdr.magic_code);
ret = -EINVAL;
goto out;
}
#endif
if (hdr.count > MAX_IMG_CNT) {
printk(KERN_ERR "gdmwm: Too many images. %d\n", hdr.count);
ret = -EINVAL;
goto out;
}
for (i = 0; i < hdr.count; i++) {
if (hdr.offset[i] > hdr.len) {
printk(KERN_ERR "gdmwm: Invalid offset. "
"Entry = %d Offset = 0x%08x "
"Image length = 0x%08x\n",
i, hdr.offset[i], hdr.len);
ret = -EINVAL;
goto out;
}
pos = hdr.offset[i];
len = filp->f_op->read(filp, (u8 *)&fw_info, sizeof(fw_info),
&pos);
if (len != sizeof(fw_info)) {
printk(KERN_ERR "gdmwm: Cannot read the FW info.\n");
ret = -EIO;
goto out;
}
array_le32_to_cpu((u32 *)&fw_info, 8);
#if 0
if ((fw_info.id & 0xfffff000) != 0x10767000) {
printk(KERN_ERR "gdmwm: Invalid FW id. 0x%08x\n",
fw_info.id);
ret = -EIO;
goto out;
}
#endif
if ((fw_info.id & 0xffff) != pid)
continue;
pos = hdr.offset[i] + fw_info.kernel_offset;
ret = download_image(usbdev, filp, &pos, fw_info.kernel_len,
DN_KERNEL_MAGIC_NUMBER);
if (ret < 0)
goto out;
printk(KERN_INFO "GCT: Kernel download success.\n");
pos = hdr.offset[i] + fw_info.rootfs_offset;
ret = download_image(usbdev, filp, &pos, fw_info.rootfs_len,
DN_ROOTFS_MAGIC_NUMBER);
if (ret < 0)
goto out;
printk(KERN_INFO "GCT: Filesystem download success.\n");
break;
}
if (i == hdr.count) {
printk(KERN_ERR "Firmware for gsk%x is not installed.\n", pid);
ret = -EINVAL;
}
out:
filp_close(filp, NULL);
restore_fs:
set_fs(fs);
kfree(tx_buf);
return ret;
}
/*#define GDM7205_PADDING 256 */
#define DOWNLOAD_CHUCK 2048
#define KERNEL_TYPE_STRING "linux"
#define FS_TYPE_STRING "rootfs"
static int em_wait_ack(struct usb_device *usbdev, int send_zlp)
{
int ack;
int ret = -1;
if (send_zlp) {
/*Send ZLP*/
ret = gdm_wibro_send(usbdev, NULL, 0);
if (ret < 0)
goto out;
}
/*Wait for ACK*/
ret = gdm_wibro_recv(usbdev, &ack, sizeof(ack));
if (ret < 0)
goto out;
out:
return ret;
}
static int em_download_image(struct usb_device *usbdev, char *path,
char *type_string)
{
struct file *filp;
struct inode *inode;
static mm_segment_t fs;
char *buf = NULL;
loff_t pos = 0;
int ret = 0;
int len, readn = 0;
#if defined(GDM7205_PADDING)
const int pad_size = GDM7205_PADDING;
#else
const int pad_size = 0;
#endif
fs = get_fs();
set_fs(get_ds());
filp = filp_open(path, O_RDONLY | O_LARGEFILE, 0);
if (IS_ERR(filp)) {
printk(KERN_ERR "Can't find %s.\n", path);
set_fs(fs);
ret = -ENOENT;
goto restore_fs;
}
inode = filp->f_dentry->d_inode;
if (!S_ISREG(inode->i_mode)) {
printk(KERN_ERR "Invalid file type: %s\n", path);
ret = -EINVAL;
goto out;
}
buf = kmalloc(DOWNLOAD_CHUCK + pad_size, GFP_KERNEL);
if (buf == NULL) {
printk(KERN_ERR "Error: kmalloc\n");
return -ENOMEM;
}
strcpy(buf+pad_size, type_string);
ret = gdm_wibro_send(usbdev, buf, strlen(type_string)+pad_size);
if (ret < 0)
goto out;
while ((len = filp->f_op->read(filp, buf+pad_size, DOWNLOAD_CHUCK,
&pos))) {
if (len < 0) {
ret = -1;
goto out;
}
readn += len;
ret = gdm_wibro_send(usbdev, buf, len+pad_size);
if (ret < 0)
goto out;
ret = em_wait_ack(usbdev, ((len+pad_size) % 512 == 0));
if (ret < 0)
goto out;
}
ret = em_wait_ack(usbdev, 1);
if (ret < 0)
goto out;
out:
filp_close(filp, NULL);
restore_fs:
set_fs(fs);
kfree(buf);
return ret;
}
static int em_fw_reset(struct usb_device *usbdev)
{
int ret;
/*Send ZLP*/
ret = gdm_wibro_send(usbdev, NULL, 0);
return ret;
}
int usb_emergency(struct usb_device *usbdev)
{
int ret;
ret = em_download_image(usbdev, KERN_PATH, KERNEL_TYPE_STRING);
if (ret < 0)
goto out;
printk(KERN_INFO "GCT Emergency: Kernel download success.\n");
ret = em_download_image(usbdev, FS_PATH, FS_TYPE_STRING);
if (ret < 0)
goto out;
printk(KERN_INFO "GCT Emergency: Filesystem download success.\n");
ret = em_fw_reset(usbdev);
out:
return ret;
}