diff -urN linux-2.6.15.1-pristine/arch/i386/mach-xbox/reset-on-eject.c linux-2.6.15.1/arch/i386/mach-xbox/reset-on-eject.c --- linux-2.6.15.1-pristine/arch/i386/mach-xbox/reset-on-eject.c 2006-12-30 18:05:33.000000000 -0800 +++ linux-2.6.15.1/arch/i386/mach-xbox/reset-on-eject.c 2007-02-25 15:24:51.000000000 -0800 @@ -12,8 +12,14 @@ * 2003-08-06 Michael Steil * removed Linux I2C dependency, now compiles * without I2C in the kernel + * 2006-12-10 Miles Nordin + * further entangle with ide-cd.c by making all + * load/eject calls fallow the same codepath as + * the ioctl's used by 'eject' and 'eject -t' from + * userland. * * Todo: add errorhandling! + * make ide-cd.ko unloadable * */ @@ -23,6 +29,9 @@ #include #include #include +#include +#include +#include #define IRQ 12 #define DRIVER_NAME "xboxejectfix" @@ -30,10 +39,6 @@ /* just some crap */ static char dev[]=DRIVER_NAME; -/* External variable from ide-cd.c that specifies whether we simulate drive - locking in software */ -extern volatile int Xbox_simulate_drive_locked; - #define BASE 0x8000 /* Power Management 1 Enable Register */ @@ -53,10 +58,33 @@ #define PM2A (BASE+0x2A) -static DECLARE_MUTEX(extsmi_sem); +static DECLARE_MUTEX_LOCKED(extsmi_sem); static DECLARE_COMPLETION(extsmi_exited); static int extsmi_pid=0; +static DECLARE_MUTEX_LOCKED(extsmi_ejector_sem); +static DECLARE_COMPLETION(extsmi_ejector_exited); +static int extsmi_ejector_pid=0; + +/* used from ide-cd.c: */ + +#include "../../../drivers/ide/ide-cd.h" + +ide_drive_t *extsmi_ide = NULL; /* pointer to first CD-ROM probed from drivers/ide/ide-cd.c */ +EXPORT_SYMBOL(extsmi_ide); + +#define EXTSMI_WANT_CLOSE (0) +#define EXTSMI_WANT_CLOSEIFOPEN (1) +#define EXTSMI_WANT_OPEN (2) + +static volatile unsigned int extsmi_iteration=0; +static volatile int extsmi_want_drive=EXTSMI_WANT_CLOSE; + +static void extsmi_ejector_process(void); +static void extsmi_process(void); +static int extsmi_ejector_thread(void *); +static int extsmi_thread(void *); + static irqreturn_t extsmi_interupt(int unused, void *dev_id, struct pt_regs *regs) { int reason; @@ -71,17 +99,83 @@ /** * Process an event. This is run in process-context. + * I don't know for sure, but I guess maybe the Xbox_SMC_write's can take a + * long time, so the original authors wanted not to block the interrupt? + * -- Miles */ static void extsmi_process(void){ int reason; + tray_position_t drive_is; + reason=Xbox_SMC_read(SMC_CMD_INTERRUPT_REASON); if(reason&TRAYBUTTON_MASK){ /* Tray button! Respond to prevent reboot! */ + /* + * opening and closing the drive with ATAPI commands can take tens + * of seconds. We need to do the actual command-sending in a separate + * thread. Otherwise, if someone double-tapped the eject button, we + * would be asleep in the ATAPI layer, wouldn't answer the PIC, and + * would be rebooted. + */ Xbox_SMC_write(SMC_CMD_INTERRUPT_RESPOND, SMC_SUBCMD_RESPOND_CONTINUE); Xbox_SMC_write(0x00, 0x0c); - /* eject unless lock simulation is being used */ - if (!Xbox_simulate_drive_locked) - Xbox_tray_eject(); + + if (!extsmi_ide) + return; + /* + * now, our job is to determine based on context what the user meant by + * jabbing the button---to open, or to close? + * + * here is what I do as a user: + * if the drive is closed, and I want it open: + * I jab the button. + * If it doesn't open instantly, I growl, curse Microsoft, and then jab + * the button two or three more times in <1sec. + * if the drive is open, and I want it closed: + * I backhand the tray and expect the drive to notice and suck in + * if the drive doesn't notice I shove the tray again. + * if it's still not closing I might press the button + * if the tray is stuck and won't budge, I press the button. I have one + * Samsung drive with a broken gear (from too vigorous backhanding?) + * that can't be shoved closed, so making the button open-only is no good. + * if the drive is neither open nor closed: + * I press the button about once per second until the tray starts moving + * in the direction I want it to, then quit pressing and wait about five + * seconds. + * If the tray won't settle and keeps moving in/out, I grab it, and stop it + * from moving. When I can feel its pressure going in the direction I want, + * I let go. + * + * ATAPI doesn't give us as much information or as low latency as the PIC control + * lines on the proprietary XBox drive, so we will just have to accomodate the above + * as best we can. + */ + drive_is = CDROM_STATE_FLAGS(extsmi_ide)->tray_position; + switch(drive_is) { + case IDECD_TRAY_CLOSED: /* + * we last sent an ATAPI close command. it succeeded. + * the drive's button is blocked by the XBox case plastic, + * so it's definitely still closed. + */ + case IDECD_TRAY_CLOSING: /* an ATAPI close command is queued, but hasn't returned. */ + extsmi_want_drive = EXTSMI_WANT_OPEN; + break; + + case IDECD_TRAY_MAYBEOPEN:/* + * we last sent an ATAPI open command. it succeeded. + * however, the user may have shoved the tray closed. + */ + extsmi_want_drive = EXTSMI_WANT_CLOSEIFOPEN; + break; + + case IDECD_TRAY_OPENING: /* an ATAPI open command is queued, but hasn't returned. */ + case IDECD_TRAY_UNKNOWN: /* the last ATAPI command failed, or we rebooted. */ + extsmi_want_drive = EXTSMI_WANT_OPEN; + break; + } + + extsmi_iteration++; + up(&extsmi_ejector_sem); } } @@ -97,6 +191,102 @@ complete_and_exit(&extsmi_exited, 0); } +/* + * this is the thread that will go to sleep for tens of seconds waiting for + * ATAPI commands to complete. the other extsmi_thread can always placate the + * PIC with sub-second latency. + */ +static int extsmi_ejector_thread(void *data){ + + daemonize("extsmi_ejector"); + strcpy(current->comm, "xbox_extsmi_ejector"); + + while (!signal_pending(current)) { + /* + * we would like to accomodate unloading ide-cd.ko, + * which means we should be somewhat ready for extsmi_ide + * to go back to being NULL. :/ obviously this is all + * wrong---we should be terminating these threads before + * ide-cd is allowed to unload, then restarting them if it + * loads again. I don't know how to do all that quickly, + * so I guess that makes me a real Linux developer. + * + * what i do instead is check after every sleepable operation + * if extsmi_ide has gone back to NULL, and try to move to + * a safe part of the state diagram if it has. so there are + * obviously race conditions associated with ide-cd.ko + * loading/unloading. + */ + while (!extsmi_ide) + down_interruptible(&extsmi_ejector_sem); + up(&extsmi_ejector_sem); + extsmi_ejector_process(); + } + + complete_and_exit(&extsmi_ejector_exited, 0); +} + +static void extsmi_ejector_process(void) { + int want_drive_open; + tray_position_t drive_is; + int iteration; + struct request_sense sense; + + down_interruptible(&CDROM_STATE_FLAGS(extsmi_ide)->tray_atapiq_sem); /* match up() at top of loop */ + do { + iteration = extsmi_iteration; + do { + up(&CDROM_STATE_FLAGS(extsmi_ide)->tray_atapiq_sem); /* now let userland ioctl have a crack at the drive */ + down_interruptible(&extsmi_ejector_sem); /* + * if we actually sleep here, extsmi_iteration will change. + * if we fall through down_ but extsmi_iteration is the same, we are just + * chewing up semaphores that got up'ed while we were sleeping on the + * ATAPI layer. + */ + if (!extsmi_ide) + return; + down_interruptible(&CDROM_STATE_FLAGS(extsmi_ide)->tray_atapiq_sem); /* + * don't make any decisions until we + * are next in line to move the tray. + */ + if (!extsmi_ide) + return; + want_drive_open = extsmi_want_drive; /* read this in one spot so our logic doesn't get confused by race conditions */ + drive_is = CDROM_STATE_FLAGS(extsmi_ide)->tray_position; + } while ((want_drive_open == EXTSMI_WANT_CLOSE && drive_is == IDECD_TRAY_CLOSED) || + (want_drive_open == EXTSMI_WANT_OPEN && drive_is == IDECD_TRAY_MAYBEOPEN && iteration == extsmi_iteration)); + switch (want_drive_open) { + case EXTSMI_WANT_OPEN: + if (CDROM_STATE_FLAGS(extsmi_ide)->door_locked) + break; + + /* need to use private eject function to skip acquiring the tray_atapiq_sem */ + (void)((struct cdrom_info *)extsmi_ide->driver_data)->private_ops->cdrom_eject(extsmi_ide, 0, &sense); + + break; + case EXTSMI_WANT_CLOSE: + (void)((struct cdrom_info *)extsmi_ide->driver_data)->private_ops->cdrom_eject(extsmi_ide, 1, &sense); + break; + case EXTSMI_WANT_CLOSEIFOPEN: + up(&extsmi_ejector_sem); /* make sure we get a second pass through the main loop */ + if ( ((struct cdrom_info *)extsmi_ide->driver_data)->devinfo.ops->drive_status( + &((struct cdrom_info *)extsmi_ide->driver_data)->devinfo, CDSL_CURRENT) == CDS_TRAY_OPEN) { + /* did someone change their mind while we were asleep checking the drive's status? */ + if (extsmi_want_drive == want_drive_open) + extsmi_want_drive = EXTSMI_WANT_CLOSE; + } else { + if (!extsmi_ide) + return; + CDROM_STATE_FLAGS(extsmi_ide)->tray_position = IDECD_TRAY_UNKNOWN; + if (extsmi_want_drive == want_drive_open) + extsmi_want_drive = EXTSMI_WANT_OPEN; + } + break; + } + } while (!signal_pending(current)); + +} + static int extsmi_init(void){ int pid; @@ -114,6 +304,16 @@ extsmi_pid = pid; + pid = kernel_thread(extsmi_ejector_thread, NULL, + CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (pid < 0) { + (void)kill_proc(extsmi_pid, SIGTERM, 1); + wait_for_completion(&extsmi_exited); + return pid; + } + + extsmi_ejector_pid = pid; + /* this shuts a lot of interrupts off! */ outw(inw(0x80e2)&0xf8c7,0x80e2); outw(0,0x80ac); @@ -136,6 +336,8 @@ /* Kill the thread */ res = kill_proc(extsmi_pid, SIGTERM, 1); wait_for_completion(&extsmi_exited); + res = kill_proc(extsmi_ejector_pid, SIGTERM, 1); + wait_for_completion(&extsmi_ejector_exited); return; } diff -urN linux-2.6.15.1-pristine/drivers/cdrom/cdrom.c linux-2.6.15.1/drivers/cdrom/cdrom.c --- linux-2.6.15.1-pristine/drivers/cdrom/cdrom.c 2006-12-30 18:05:33.000000000 -0800 +++ linux-2.6.15.1/drivers/cdrom/cdrom.c 2007-01-01 20:30:09.000000000 -0800 @@ -2206,11 +2206,24 @@ struct cdrom_device_ops *cdo = cdi->ops; int ret; - /* Try the generic SCSI command ioctl's first.. */ + switch(cmd) { + case CDROMEJECT: + case CDROMCLOSETRAY: + /* + * we use ops->tray_move for open_for_data() and other + * functions in this file, so we should not then switch + * to the generic SCSI layer for the ioctl. + */ + break; + default: + + /* Otherwise, try the generic SCSI command ioctl's first. */ ret = scsi_cmd_ioctl(file, ip->i_bdev->bd_disk, cmd, (void __user *)arg); if (ret != -ENOTTY) return ret; + } + /* the first few commands do not deal with audio drive_info, but only with routines in cdrom device operations. */ switch (cmd) { diff -urN linux-2.6.15.1-pristine/drivers/ide/ide-cd.c linux-2.6.15.1/drivers/ide/ide-cd.c --- linux-2.6.15.1-pristine/drivers/ide/ide-cd.c 2006-12-30 18:05:33.000000000 -0800 +++ linux-2.6.15.1/drivers/ide/ide-cd.c 2007-02-25 15:13:12.000000000 -0800 @@ -321,6 +321,7 @@ #include #include #include +#include #include "ide-cd.h" @@ -354,10 +355,10 @@ #ifdef CONFIG_X86_XBOX #include -/* Global flag indicating whether to simulate Xbox drive locking in - * software. There should only be one Xbox drive in a system! This - * variable is externally referenced by arch/i386/kernel/xboxejectfix.c. */ -volatile int Xbox_simulate_drive_locked = 0; +extern volatile ide_drive_t *extsmi_ide; /* + * stuff first CD-ROM probed in here so + * arch/i386/mach-xbox/reset-on-eject.c can find us + */ #endif /* CONFIG_X86_XBOX */ /**************************************************************************** @@ -2087,7 +2088,6 @@ request in software. (See arch/i386/kernel/xboxejectfix.c) */ if (CDROM_CONFIG_FLAGS(drive)->xbox_drive && machine_is_xbox) { CDROM_STATE_FLAGS(drive)->door_locked = lockflag; - Xbox_simulate_drive_locked = lockflag; return 0; } #endif /* CONFIG_X86_XBOX */ @@ -2132,6 +2132,7 @@ { struct request req; char loej = 0x02; + int stat; if (CDROM_CONFIG_FLAGS(drive)->no_eject && !ejectflag) return -EDRIVE_CANT_DO_THIS; @@ -2148,9 +2149,10 @@ if (machine_is_xbox && CDROM_CONFIG_FLAGS(drive)->xbox_drive && CDROM_CONFIG_FLAGS(drive)->xbox_eject) { if (ejectflag) { + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_CLOSED; Xbox_tray_load(); } else { - Xbox_simulate_drive_locked = 0; + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_MAYBEOPEN; Xbox_tray_eject(); } return 0; @@ -2165,7 +2167,22 @@ req.sense = sense; req.cmd[0] = GPCMD_START_STOP_UNIT; req.cmd[4] = loej | (ejectflag != 0); - return cdrom_queue_packet_command(drive, &req); + if (ejectflag) { + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_CLOSING; + stat = cdrom_queue_packet_command(drive, &req); + if (!stat) + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_CLOSED; + else + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_UNKNOWN; + } else { + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_OPENING; + stat = cdrom_queue_packet_command(drive, &req); + if (!stat) + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_MAYBEOPEN; + else + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_UNKNOWN; + } + return stat; } static int cdrom_read_capacity(ide_drive_t *drive, unsigned long *capacity, @@ -2668,6 +2685,16 @@ { ide_drive_t *drive = cdi->handle; struct request_sense sense; + int stat; + + /* + * by only passing one of these commands at a time to cdrom_queue_packet_command, + * 1. we keep the CDROM_STATE_FLAGS(drive)->tray_position status valid + * 2. we ensure that in arch/i386/mach-xbox/reset-on-eject.c, it can send a + * drive_status first and a tray_move second with no other intervening + * tray move + */ + down_interruptible(&CDROM_STATE_FLAGS(drive)->tray_atapiq_sem); if (position) { int stat = cdrom_lockdoor(drive, 0, &sense); @@ -2675,7 +2702,10 @@ return stat; } - return cdrom_eject(drive, !position, &sense); + stat = cdrom_eject(drive, !position, &sense); + + up(&CDROM_STATE_FLAGS(drive)->tray_atapiq_sem); + return stat; } static @@ -2908,12 +2938,17 @@ .generic_packet = ide_cdrom_packet, }; +static struct ide_cd_private_ops ide_cdrom_pops = { + .cdrom_eject = cdrom_eject, +}; + static int ide_cdrom_register (ide_drive_t *drive, int nslots) { struct cdrom_info *info = drive->driver_data; struct cdrom_device_info *devinfo = &info->devinfo; devinfo->ops = &ide_cdrom_dops; + info->private_ops = &ide_cdrom_pops; devinfo->mask = 0; devinfo->speed = CDROM_STATE_FLAGS(drive)->current_speed; devinfo->capacity = nslots; @@ -2941,6 +2976,10 @@ devinfo->mask |= CDC_MO_DRIVE; devinfo->disk = info->disk; +#ifdef CONFIG_X86_XBOX + if (extsmi_ide == NULL) + extsmi_ide = drive; +#endif return register_cdrom(devinfo); } @@ -3164,7 +3203,8 @@ CDROM_STATE_FLAGS(drive)->media_changed = 1; CDROM_STATE_FLAGS(drive)->toc_valid = 0; CDROM_STATE_FLAGS(drive)->door_locked = 0; - + CDROM_STATE_FLAGS(drive)->tray_position = IDECD_TRAY_UNKNOWN; + init_MUTEX(&CDROM_STATE_FLAGS(drive)->tray_atapiq_sem); #if NO_DOOR_LOCKING CDROM_CONFIG_FLAGS(drive)->no_doorlock = 1; #else @@ -3299,7 +3339,6 @@ the eject interrupt (see arch/i386/kernel/xboxejectfix.c). */ CDROM_CONFIG_FLAGS(drive)->no_doorlock = 0; CDROM_CONFIG_FLAGS(drive)->no_eject = 0; - Xbox_simulate_drive_locked = 0; } } #endif @@ -3375,6 +3414,11 @@ ide_drive_t *drive = info->drive; struct gendisk *g = info->disk; +#ifdef CONFIG_X86_XBOX + if (extsmi_ide == drive) + extsmi_ide = NULL; +#endif + kfree(info->buffer); kfree(info->toc); kfree(info->changer_info); @@ -3473,9 +3517,16 @@ { struct block_device *bdev = inode->i_bdev; struct cdrom_info *info = ide_cd_g(bdev->bd_disk); - int err; + int err = -EINVAL; + switch(cmd) { + case CDROMEJECT: + case CDROMCLOSETRAY: + /* do these ourselves. */ + break; + default: err = generic_ide_ioctl(info->drive, file, bdev, cmd, arg); + } if (err == -EINVAL) err = cdrom_ioctl(file, &info->devinfo, inode, cmd, arg); diff -urN linux-2.6.15.1-pristine/drivers/ide/ide-cd.h linux-2.6.15.1/drivers/ide/ide-cd.h --- linux-2.6.15.1-pristine/drivers/ide/ide-cd.h 2006-12-30 18:05:33.000000000 -0800 +++ linux-2.6.15.1/drivers/ide/ide-cd.h 2007-01-01 15:05:21.000000000 -0800 @@ -7,8 +7,11 @@ #ifndef _IDE_CD_H #define _IDE_CD_H +#include #include +#include #include +#include /* Turn this on to have the driver print out the meanings of the ATAPI error codes. This will use up additional kernel-space @@ -103,6 +106,13 @@ /* State flags. These give information about the current state of the drive, and will change during normal operation. */ +typedef enum { + IDECD_TRAY_CLOSED = 0, + IDECD_TRAY_CLOSING = 1, + IDECD_TRAY_MAYBEOPEN = 2, + IDECD_TRAY_OPENING = 3, + IDECD_TRAY_UNKNOWN = 4, +} tray_position_t; struct ide_cd_state_flags { __u8 media_changed : 1; /* Driver has noticed a media change. */ __u8 toc_valid : 1; /* Saved TOC information is current. */ @@ -110,6 +120,13 @@ __u8 writing : 1; /* the drive is currently writing */ __u8 reserved : 4; byte current_speed; /* Current speed of the drive */ + tray_position_t tray_position; + struct semaphore tray_atapiq_sem; +}; + +/* callback for arch/i386/mach-xbox/reset-on-eject.c */ +struct ide_cd_private_ops { + int (*cdrom_eject) (ide_drive_t *, int, struct request_sense *); }; #define CDROM_STATE_FLAGS(drive) (&(((struct cdrom_info *)(drive->driver_data))->state_flags)) @@ -491,6 +508,7 @@ struct ide_cd_config_flags config_flags; struct ide_cd_state_flags state_flags; + struct ide_cd_private_ops *private_ops; /* Per-device info needed by cdrom.c generic driver. */ struct cdrom_device_info devinfo; diff -urN linux-2.6.15.1-pristine/include/asm-i386/mach-xbox/setup_arch_pre.h linux-2.6.15.1/include/asm-i386/mach-xbox/setup_arch_pre.h --- linux-2.6.15.1-pristine/include/asm-i386/mach-xbox/setup_arch_pre.h 2006-12-30 18:05:33.000000000 -0800 +++ linux-2.6.15.1/include/asm-i386/mach-xbox/setup_arch_pre.h 2007-02-25 14:04:12.000000000 -0800 @@ -12,6 +12,7 @@ //int CLOCK_TICK_RATE; int machine_is_xbox = 0; +EXPORT_SYMBOL(machine_is_xbox); static inline void arch_setup_xbox(void) { outl(0x80000000, 0xcf8); --- linux-2.6.15.1-pristine/arch/i386/defconfig 2006-12-30 18:05:33.000000000 -0800 +++ linux-2.6.15.1/arch/i386/defconfig 2007-02-25 21:16:31.000000000 -0800 @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Linux kernel version: 2.6.15.1 -# Fri Jan 20 06:51:37 2006 +# Sun Feb 25 15:24:58 2007 # CONFIG_X86_32=y CONFIG_SEMAPHORE_SLEEPERS=y @@ -442,17 +442,17 @@ # # ATA/ATAPI/MFM/RLL support # -CONFIG_IDE=y -CONFIG_BLK_DEV_IDE=y +CONFIG_IDE=m +CONFIG_BLK_DEV_IDE=m # # Please see Documentation/ide.txt for help/info on IDE drives # # CONFIG_BLK_DEV_IDE_SATA is not set # CONFIG_BLK_DEV_HD_IDE is not set -CONFIG_BLK_DEV_IDEDISK=y +CONFIG_BLK_DEV_IDEDISK=m # CONFIG_IDEDISK_MULTI_MODE is not set -CONFIG_BLK_DEV_IDECD=y +CONFIG_BLK_DEV_IDECD=m # CONFIG_BLK_DEV_IDETAPE is not set # CONFIG_BLK_DEV_IDEFLOPPY is not set CONFIG_BLK_DEV_IDESCSI=m @@ -466,7 +466,7 @@ CONFIG_BLK_DEV_IDEPCI=y CONFIG_IDEPCI_SHARE_IRQ=y # CONFIG_BLK_DEV_OFFBOARD is not set -CONFIG_BLK_DEV_GENERIC=y +CONFIG_BLK_DEV_GENERIC=m # CONFIG_BLK_DEV_OPTI621 is not set # CONFIG_BLK_DEV_RZ1000 is not set CONFIG_BLK_DEV_IDEDMA_PCI=y @@ -475,7 +475,7 @@ # CONFIG_IDEDMA_ONLYDISK is not set # CONFIG_BLK_DEV_AEC62XX is not set # CONFIG_BLK_DEV_ALI15X3 is not set -CONFIG_BLK_DEV_AMD74XX=y +CONFIG_BLK_DEV_AMD74XX=m # CONFIG_BLK_DEV_ATIIXP is not set # CONFIG_BLK_DEV_CMD64X is not set # CONFIG_BLK_DEV_TRIFLEX is not set