Date: Fri, 12 Aug 2005 23:50:10 GMT From: Bruce Evans <bde@zeta.org.au> To: freebsd-i386@FreeBSD.org Subject: Re: i386/84842: i386_set_ioperm(2) timing issue Message-ID: <200508122350.j7CNoAJc068342@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
The following reply was made to PR i386/84842; it has been noted by GNATS. From: Bruce Evans <bde@zeta.org.au> To: Bruce Evans <bde@FreeBSD.org> Cc: arundel@h3c.de, freebsd-gnats-submit@FreeBSD.org Subject: Re: i386/84842: i386_set_ioperm(2) timing issue Date: Sat, 13 Aug 2005 09:44:33 +1000 (EST) On Fri, 12 Aug 2005, Bruce Evans wrote: > The problem seems to be that the TSS is not loaded by the syscall. The > i/o permissions bitmap is in the TSS and I think think the TSS must be > reloaded for the new bitmap to be seen. The TSS is reloaded on the next > context switch but doesn't seem to be loaded anywhere else in normal > execution (it is also loaded at boot time and for vm86 BIOS calls and > returns). > > Try adding an ltr(gsel_tss) near the end of i386_set_ioperm(). Further reading and testing showed that the bug is a relatively new one and fixing it cleanly is not so easy. The CPU apparently examines the permissions bitmap on every access (that's one reason i/o accesses are so slow :-), so the TSS [register] doesn't need to be reloaded after every change. However, for the first access the bitmap is usually empty since the loading of the new TSS that holds the bitmap has been broken by an optimization. From an old version of i386/sys_machdep.c: % int % i386_extend_pcb(struct thread *td) % { % ... % /* switch to the new TSS after syscall completes */ % td->td_flags |= TDF_NEEDRESCHED; This was broken by optimizing null context switches. Now the switch to the new TSS doesn't normally actually occur after the syscall completes, since the scheduler normally reschedules the same thread and mi_switch() now avoids calling cpu_switch() in this case. % ... % } % % static int % i386_set_ioperm(td, args) % struct thread *td; % char *args; % { % .. % if (td->td_pcb->pcb_ext == 0) % if ((error = i386_extend_pcb(td)) != 0) % return (error); % iomap = (char *)td->td_pcb->pcb_ext->ext_iomap; This extends the pcb on the first call to i386_set_ioperm() for a thread, so if the TSS were reloaded after the call as intended then the first call would be less broken than subsequent calls. % % if (ua.start + ua.length > IOPAGES * PAGE_SIZE * NBBY) % return (EINVAL); % % for (i = ua.start; i < ua.start + ua.length; i++) { % if (ua.enable) % iomap[i >> 3] &= ~(1 << (i & 7)); % else % iomap[i >> 3] |= (1 << (i & 7)); % } Testing shows than an ltr() is not needed here. It is sufficient to sleep (in the application) after the first call so that the new TSS gets loaded. For subsequent calls, the TSS descriptor doesn't change, and the TSS apparently doesn't need to be reloaded to get changes to the contents of the TSS seen. % return (error); % } For a quick fix, a tsleep(..., 1) in 386_extend_pcb() should work as well as a sleep in the application (unless/until somone modifies short sleeps to not switch). Bruce
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200508122350.j7CNoAJc068342>