diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 8285bb57eb95..bc284bdb1b15 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -208,6 +208,7 @@ #include #include #include +#include #include #include #include @@ -228,6 +229,12 @@ #define FSG_DRIVER_DESC "Mass Storage Function" #define FSG_DRIVER_VERSION "2009/09/11" +enum fsg_restart_type { + FSG_RESTART_NONE, + FSG_REBOOT, + FSG_REBOOT_BL, +}; + static const char fsg_string_interface[] = "Mass Storage"; #include "storage_common.h" @@ -330,6 +337,8 @@ struct fsg_common { char name[FSG_MAX_LUNS][LUN_NAME_LEN]; struct kref ref; struct timer_list vfs_timer; + enum fsg_restart_type restart_type; + struct delayed_work restart_work; }; struct fsg_dev { @@ -2206,6 +2215,34 @@ static int do_scsi_command(struct fsg_common *common) if (reply == 0) reply = do_write(common); break; + case SC_REBOOT: + common->data_size_from_cmnd = 0; + reply = check_command(common, common->cmnd_size, + DATA_DIR_NONE, 0, 0, "REBOOT BL"); + if (reply == 0) { + common->curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } else { + pr_err("Triggered Reboot from SCSI Command\n"); + common->restart_type = FSG_REBOOT; + schedule_delayed_work(&common->restart_work, + msecs_to_jiffies(1000)); + } + break; + case SC_REBOOT_2: + common->data_size_from_cmnd = 0; + reply = check_command(common, common->cmnd_size, + DATA_DIR_NONE, 0, 0, "REBOOT"); + if (reply == 0) { + common->curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } else { + pr_err("Triggered Reboot BL from SCSI Command\n"); + common->restart_type = FSG_REBOOT_BL; + schedule_delayed_work(&common->restart_work, + msecs_to_jiffies(1000)); + } + break; /* * Some mandatory commands that we recognize but don't implement. @@ -2870,6 +2907,32 @@ static inline int fsg_num_buffers_validate(unsigned int fsg_num_buffers) return -EINVAL; } +static int disable_restarts; +module_param(disable_restarts, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_restarts, "Disable SCSI Initiated Reboots"); +static void fsg_restart_work(struct work_struct *work) +{ + struct fsg_common *common = container_of(work, + struct fsg_common, + restart_work.work); + + if (disable_restarts) { + pr_err("SCSI Reboots Disabled\n"); + return; + } + + switch (common->restart_type) { + case FSG_REBOOT: + kernel_restart(""); + break; + case FSG_REBOOT_BL: + kernel_restart("bootloader"); + break; + default: + break; + } +} + static struct fsg_common *fsg_common_setup(struct fsg_common *common) { if (!common) { @@ -2885,6 +2948,7 @@ static struct fsg_common *fsg_common_setup(struct fsg_common *common) kref_init(&common->ref); init_completion(&common->thread_notifier); init_waitqueue_head(&common->fsg_wait); + INIT_DELAYED_WORK(&common->restart_work, fsg_restart_work); common->state = FSG_STATE_TERMINATED; return common; diff --git a/include/scsi/scsi.h b/include/scsi/scsi.h index e7019dbe5779..ffeab2ddd7e0 100644 --- a/include/scsi/scsi.h +++ b/include/scsi/scsi.h @@ -192,6 +192,10 @@ enum scsi_timeouts { #define ATA_16 0x85 /* 16-byte pass-thru */ #define ATA_12 0xa1 /* 12-byte pass-thru */ +/* Vendor Specific */ +#define SC_REBOOT 0xd7 +#define SC_REBOOT_2 0xd8 + /* * SCSI command lengths */