Date: Tue, 5 May 2009 17:43:01 -0400 From: Jung-uk Kim <jkim@FreeBSD.org> To: Andriy Gapon <avg@icyb.net.ua> Cc: Alan Amesbury <amesbury@umn.edu>, freebsd-acpi@freebsd.org, freebsd-stable@freebsd.org, John Baldwin <jhb@freebsd.org> Subject: Re: Garbled output from kgdb? Message-ID: <200905051743.03520.jkim@FreeBSD.org> In-Reply-To: <200905051609.38689.jkim@FreeBSD.org> References: <49F8B859.7060908@umn.edu> <4A006E9A.7060806@icyb.net.ua> <200905051609.38689.jkim@FreeBSD.org>
next in thread | previous in thread | raw e-mail | index | archive | help
--Boundary-00=_nLLAKMdakiVOZxJ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit Content-Disposition: inline On Tuesday 05 May 2009 04:09 pm, Jung-uk Kim wrote: > On Tuesday 05 May 2009 12:51 pm, Andriy Gapon wrote: > > BTW, this issue seems to be fixed in Jung-uk's acpi patches for > > newer acpica imports, but it is not fixed both in stable/7 and > > head. > > Yes, it was fixed in my patchsets long ago, which uses spin lock > for AcpiOsAcquireLock(). :-) The attached patch is for -STABLE. Note that it is only compile tested on amd64. Jung-uk Kim --Boundary-00=_nLLAKMdakiVOZxJ Content-Type: text/plain; charset="iso-8859-1"; name="OsdSynch.diff" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="OsdSynch.diff" --- sys/dev/acpica/Osd/OsdSynch.c (revision 191834) +++ sys/dev/acpica/Osd/OsdSynch.c (working copy) @@ -1,6 +1,7 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi + * Copyright (c) 2007-2009 Jung-uk Kim <jkim@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,365 +36,313 @@ #include <contrib/dev/acpica/acpi.h> #include "opt_acpi.h" +#include <sys/condvar.h> #include <sys/kernel.h> -#include <sys/malloc.h> -#include <sys/sysctl.h> #include <sys/lock.h> +#include <sys/malloc.h> #include <sys/mutex.h> +#include <sys/sx.h> -#define _COMPONENT ACPI_OS_SERVICES +#define _COMPONENT ACPI_OS_SERVICES ACPI_MODULE_NAME("SYNCH") MALLOC_DEFINE(M_ACPISEM, "acpisem", "ACPI semaphore"); -#define AS_LOCK(as) mtx_lock(&(as)->as_mtx) -#define AS_UNLOCK(as) mtx_unlock(&(as)->as_mtx) +/* Convert microseconds to hz. */ +static int +timeout2hz(long timo) +{ + struct timeval tv; + tv.tv_sec = timo / 1000000; + tv.tv_usec = timo % 1000000; + + return (tvtohz(&tv)); +} + +/* Adjust timeout from the previous uptime. */ +static long +adjust_timeout(long *tmop, struct timeval *tvp) +{ + struct timeval now, slept; + + getmicrouptime(&now); + slept = now; + timevalsub(&slept, tvp); + *tmop -= slept.tv_sec * 1000000 + slept.tv_usec; + *tvp = now; + + return (*tmop); +} + /* - * Simple counting semaphore implemented using a mutex. (Subsequently used - * in the OSI code to implement a mutex. Go figure.) + * ACPI_SEMAPHORE: a sleepable counting semaphore */ struct acpi_semaphore { - struct mtx as_mtx; - UINT32 as_units; - UINT32 as_maxunits; - UINT32 as_pendings; - UINT32 as_resetting; - UINT32 as_timeouts; + struct sx as_lock; + char as_name[32]; + struct cv as_cv; + UINT32 as_units; + UINT32 as_maxunits; }; -/* Default number of maximum pending threads. */ -#ifndef ACPI_NO_SEMAPHORES -#ifndef ACPI_SEMAPHORES_MAX_PENDING -#define ACPI_SEMAPHORES_MAX_PENDING 4 -#endif - -static int acpi_semaphore_debug = 0; -TUNABLE_INT("debug.acpi_semaphore_debug", &acpi_semaphore_debug); -SYSCTL_DECL(_debug_acpi); -SYSCTL_INT(_debug_acpi, OID_AUTO, semaphore_debug, CTLFLAG_RW, - &acpi_semaphore_debug, 0, "Enable ACPI semaphore debug messages"); -#endif /* !ACPI_NO_SEMAPHORES */ - ACPI_STATUS AcpiOsCreateSemaphore(UINT32 MaxUnits, UINT32 InitialUnits, ACPI_SEMAPHORE *OutHandle) { -#ifndef ACPI_NO_SEMAPHORES - struct acpi_semaphore *as; + struct acpi_semaphore *as; - ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); - if (OutHandle == NULL) - return_ACPI_STATUS (AE_BAD_PARAMETER); - if (InitialUnits > MaxUnits) - return_ACPI_STATUS (AE_BAD_PARAMETER); + if (OutHandle == NULL || InitialUnits > MaxUnits) + return_ACPI_STATUS (AE_BAD_PARAMETER); - if ((as = malloc(sizeof(*as), M_ACPISEM, M_NOWAIT | M_ZERO)) == NULL) - return_ACPI_STATUS (AE_NO_MEMORY); + if ((as = malloc(sizeof(*as), M_ACPISEM, M_NOWAIT | M_ZERO)) == NULL) + return_ACPI_STATUS (AE_NO_MEMORY); - mtx_init(&as->as_mtx, "ACPI semaphore", NULL, MTX_DEF); - as->as_units = InitialUnits; - as->as_maxunits = MaxUnits; - as->as_pendings = as->as_resetting = as->as_timeouts = 0; + snprintf(as->as_name, sizeof(as->as_name), "ACPI sema (%p)", as); + sx_init(&as->as_lock, as->as_name); + cv_init(&as->as_cv, as->as_name); + as->as_units = InitialUnits; + as->as_maxunits = MaxUnits; - ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, - "created semaphore %p max %d, initial %d\n", - as, InitialUnits, MaxUnits)); + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "created semaphore %p, max %u, initial %u\n", + as, InitialUnits, MaxUnits)); - *OutHandle = (ACPI_HANDLE)as; -#else - *OutHandle = (ACPI_HANDLE)OutHandle; -#endif /* !ACPI_NO_SEMAPHORES */ + *OutHandle = (ACPI_SEMAPHORE)as; - return_ACPI_STATUS (AE_OK); + return_ACPI_STATUS (AE_OK); } ACPI_STATUS AcpiOsDeleteSemaphore(ACPI_SEMAPHORE Handle) { -#ifndef ACPI_NO_SEMAPHORES - struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; + struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; - ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); - ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "destroyed semaphore %p\n", as)); - mtx_destroy(&as->as_mtx); - free(Handle, M_ACPISEM); -#endif /* !ACPI_NO_SEMAPHORES */ + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "delete semaphore %p\n", as)); - return_ACPI_STATUS (AE_OK); + if (as != NULL) { + sx_destroy(&as->as_lock); + cv_destroy(&as->as_cv); + free(as, M_ACPISEM); + return_ACPI_STATUS (AE_OK); + } else + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "cannot delete null semaphore\n")); + + return_ACPI_STATUS (AE_BAD_PARAMETER); } -/* - * This implementation has a bug, in that it has to stall for the entire - * timeout before it will return AE_TIME. A better implementation would - * use getmicrotime() to correctly adjust the timeout after being woken up. - */ ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units, UINT16 Timeout) { -#ifndef ACPI_NO_SEMAPHORES - ACPI_STATUS result; - struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; - int rv, tmo; - struct timeval timeouttv, currenttv, timelefttv; + struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; + struct timeval tv; + long tmo; - ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); - if (as == NULL) - return_ACPI_STATUS (AE_BAD_PARAMETER); + if (as == NULL || Units == 0) + return_ACPI_STATUS (AE_BAD_PARAMETER); - if (cold) - return_ACPI_STATUS (AE_OK); + sx_xlock(&as->as_lock); + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "get %u units from semaphore %p (has %u), timeout %u\n", + Units, as, as->as_units, Timeout)); -#if 0 - if (as->as_units < Units && as->as_timeouts > 10) { - printf("%s: semaphore %p too many timeouts, resetting\n", __func__, as); - AS_LOCK(as); - as->as_units = as->as_maxunits; - if (as->as_pendings) - as->as_resetting = 1; - as->as_timeouts = 0; - wakeup(as); - AS_UNLOCK(as); - return_ACPI_STATUS (AE_TIME); - } - - if (as->as_resetting) - return_ACPI_STATUS (AE_TIME); -#endif - - /* a timeout of ACPI_WAIT_FOREVER means "forever" */ - if (Timeout == ACPI_WAIT_FOREVER) { - tmo = 0; - timeouttv.tv_sec = ((0xffff/1000) + 1); /* cf. ACPI spec */ - timeouttv.tv_usec = 0; - } else { - /* compute timeout using microseconds per tick */ - tmo = (Timeout * 1000) / (1000000 / hz); - if (tmo <= 0) - tmo = 1; - timeouttv.tv_sec = Timeout / 1000; - timeouttv.tv_usec = (Timeout % 1000) * 1000; - } - - /* calculate timeout value in timeval */ - getmicrotime(¤ttv); - timevaladd(&timeouttv, ¤ttv); - - AS_LOCK(as); - ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, - "get %d units from semaphore %p (has %d), timeout %d\n", - Units, as, as->as_units, Timeout)); - for (;;) { if (as->as_maxunits == ACPI_NO_UNIT_LIMIT) { - result = AE_OK; - break; + sx_xunlock(&as->as_lock); + return_ACPI_STATUS (AE_OK); } - if (as->as_units >= Units) { - as->as_units -= Units; - result = AE_OK; - break; + if (as->as_maxunits < Units) { + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "exceeded max units %u\n", + as->as_maxunits)); + sx_xunlock(&as->as_lock); + return_ACPI_STATUS (AE_LIMIT); } - /* limit number of pending threads */ - if (as->as_pendings >= ACPI_SEMAPHORES_MAX_PENDING) { - result = AE_TIME; - break; + switch (Timeout) { + case ACPI_DO_NOT_WAIT: + if (as->as_units >= Units) { + as->as_units -= Units; + sx_xunlock(&as->as_lock); + return_ACPI_STATUS (AE_OK); + } + break; + case ACPI_WAIT_FOREVER: + while (as->as_units < Units) + cv_wait(&as->as_cv, &as->as_lock); + as->as_units -= Units; + sx_xunlock(&as->as_lock); + return_ACPI_STATUS (AE_OK); + default: + tmo = (long)Timeout * 1000; + getmicrouptime(&tv); + do { + if (as->as_units >= Units) { + as->as_units -= Units; + sx_xunlock(&as->as_lock); + return_ACPI_STATUS (AE_OK); + } + if (cv_timedwait(&as->as_cv, &as->as_lock, + timeout2hz(tmo)) == EWOULDBLOCK) + break; + } while (adjust_timeout(&tmo, &tv) > 0); } + sx_xunlock(&as->as_lock); - /* if timeout values of zero is specified, return immediately */ - if (Timeout == 0) { - result = AE_TIME; - break; - } + return_ACPI_STATUS (AE_TIME); +} - ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, - "semaphore blocked, calling msleep(%p, %p, %d, \"acsem\", %d)\n", - as, &as->as_mtx, PCATCH, tmo)); +ACPI_STATUS +AcpiOsSignalSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units) +{ + struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; - as->as_pendings++; + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); - if (acpi_semaphore_debug) { - printf("%s: Sleep %d, pending %d, semaphore %p, thread %d\n", - __func__, Timeout, as->as_pendings, as, AcpiOsGetThreadId()); - } + if (as == NULL || Units == 0) + return_ACPI_STATUS (AE_BAD_PARAMETER); - rv = msleep(as, &as->as_mtx, PCATCH, "acsem", tmo); + sx_xlock(&as->as_lock); + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "return %u units to semaphore %p (has %u)\n", + Units, as, as->as_units)); - as->as_pendings--; - -#if 0 - if (as->as_resetting) { - /* semaphore reset, return immediately */ - if (as->as_pendings == 0) { - as->as_resetting = 0; - } - result = AE_TIME; - break; + if (as->as_maxunits == ACPI_NO_UNIT_LIMIT) { + sx_xunlock(&as->as_lock); + return_ACPI_STATUS (AE_OK); } -#endif - - ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "msleep(%d) returned %d\n", tmo, rv)); - if (rv == EWOULDBLOCK) { - result = AE_TIME; - break; + if (as->as_maxunits < Units || + as->as_maxunits - Units < as->as_units) { + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "exceeded max units %u\n", + as->as_maxunits)); + sx_xunlock(&as->as_lock); + return_ACPI_STATUS (AE_LIMIT); } - /* check if we already awaited enough */ - timelefttv = timeouttv; - getmicrotime(¤ttv); - timevalsub(&timelefttv, ¤ttv); - if (timelefttv.tv_sec < 0) { - ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "await semaphore %p timeout\n", - as)); - result = AE_TIME; - break; - } + as->as_units += Units; + cv_broadcast(&as->as_cv); + sx_xunlock(&as->as_lock); - /* adjust timeout for the next sleep */ - tmo = (timelefttv.tv_sec * 1000000 + timelefttv.tv_usec) / - (1000000 / hz); - if (tmo <= 0) - tmo = 1; - - if (acpi_semaphore_debug) { - printf("%s: Wakeup timeleft(%jd, %lu), tmo %u, sem %p, thread %d\n", - __func__, (intmax_t)timelefttv.tv_sec, timelefttv.tv_usec, tmo, as, - AcpiOsGetThreadId()); - } - } - - if (acpi_semaphore_debug) { - if (result == AE_TIME && Timeout > 0) { - printf("%s: Timeout %d, pending %d, semaphore %p\n", - __func__, Timeout, as->as_pendings, as); - } - if (result == AE_OK && (as->as_timeouts > 0 || as->as_pendings > 0)) { - printf("%s: Acquire %d, units %d, pending %d, sem %p, thread %d\n", - __func__, Units, as->as_units, as->as_pendings, as, - AcpiOsGetThreadId()); - } - } - - if (result == AE_TIME) - as->as_timeouts++; - else - as->as_timeouts = 0; - - AS_UNLOCK(as); - return_ACPI_STATUS (result); -#else - return_ACPI_STATUS (AE_OK); -#endif /* !ACPI_NO_SEMAPHORES */ + return_ACPI_STATUS (AE_OK); } +/* + * ACPI_SPINLOCK: a non-sleepable spinlock + */ +struct acpi_spinlock { + struct mtx al_lock; + char al_name[32]; + int al_nested; +}; + ACPI_STATUS -AcpiOsSignalSemaphore(ACPI_SEMAPHORE Handle, UINT32 Units) +AcpiOsCreateLock(ACPI_SPINLOCK *OutHandle) { -#ifndef ACPI_NO_SEMAPHORES - struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; + struct acpi_spinlock *al; - ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); - if (as == NULL) - return_ACPI_STATUS(AE_BAD_PARAMETER); + if (OutHandle == NULL) + return_ACPI_STATUS (AE_BAD_PARAMETER); + al = malloc(sizeof(*al), M_ACPISEM, M_NOWAIT | M_ZERO); + if (al == NULL) + return_ACPI_STATUS (AE_NO_MEMORY); - AS_LOCK(as); - ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, - "return %d units to semaphore %p (has %d)\n", - Units, as, as->as_units)); - if (as->as_maxunits != ACPI_NO_UNIT_LIMIT) { - as->as_units += Units; - if (as->as_units > as->as_maxunits) - as->as_units = as->as_maxunits; - } + /* Build a unique name based on the address of the handle. */ + if (OutHandle == &AcpiGbl_GpeLock) + snprintf(al->al_name, sizeof(al->al_name), "ACPI lock (GPE)"); + else if (OutHandle == &AcpiGbl_HardwareLock) + snprintf(al->al_name, sizeof(al->al_name), "ACPI lock (HW)"); + else + snprintf(al->al_name, sizeof(al->al_name), "ACPI lock (%p)", + OutHandle); + mtx_init(&al->al_lock, al->al_name, NULL, MTX_SPIN); - if (acpi_semaphore_debug && (as->as_timeouts > 0 || as->as_pendings > 0)) { - printf("%s: Release %d, units %d, pending %d, semaphore %p, thread %d\n", - __func__, Units, as->as_units, as->as_pendings, as, AcpiOsGetThreadId()); - } + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "created %s\n", al->al_name)); - wakeup(as); - AS_UNLOCK(as); -#endif /* !ACPI_NO_SEMAPHORES */ + *OutHandle = (ACPI_SPINLOCK)al; - return_ACPI_STATUS (AE_OK); + return_ACPI_STATUS (AE_OK); } -/* Combined mutex + mutex name storage since the latter must persist. */ -struct acpi_spinlock { - struct mtx lock; - char name[32]; -}; - -ACPI_STATUS -AcpiOsCreateLock (ACPI_SPINLOCK *OutHandle) +void +AcpiOsDeleteLock(ACPI_SPINLOCK Handle) { - struct acpi_spinlock *h; + struct acpi_spinlock *al = (struct acpi_spinlock *)Handle; - if (OutHandle == NULL) - return (AE_BAD_PARAMETER); - h = malloc(sizeof(*h), M_ACPISEM, M_NOWAIT | M_ZERO); - if (h == NULL) - return (AE_NO_MEMORY); + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); - /* Build a unique name based on the address of the handle. */ - if (OutHandle == &AcpiGbl_GpeLock) - snprintf(h->name, sizeof(h->name), "acpi subsystem GPE lock"); - else if (OutHandle == &AcpiGbl_HardwareLock) - snprintf(h->name, sizeof(h->name), "acpi subsystem HW lock"); - else - snprintf(h->name, sizeof(h->name), "acpi subsys %p", OutHandle); - mtx_init(&h->lock, h->name, NULL, MTX_DEF); - *OutHandle = (ACPI_SPINLOCK)h; - return (AE_OK); + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "delete %s\n", al->al_name)); + + if (al != NULL) { + mtx_destroy(&al->al_lock); + free(al, M_ACPISEM); + } else + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "cannot delete null spinlock\n")); } -void -AcpiOsDeleteLock (ACPI_SPINLOCK Handle) +ACPI_CPU_FLAGS +AcpiOsAcquireLock(ACPI_SPINLOCK Handle) { - struct acpi_spinlock *h = (struct acpi_spinlock *)Handle; + struct acpi_spinlock *al = (struct acpi_spinlock *)Handle; - if (Handle == NULL) - return; - mtx_destroy(&h->lock); - free(h, M_ACPISEM); -} + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); -/* - * The Flags parameter seems to state whether or not caller is an ISR - * (and thus can't block) but since we have ithreads, we don't worry - * about potentially blocking. - */ -ACPI_NATIVE_UINT -AcpiOsAcquireLock (ACPI_SPINLOCK Handle) -{ - struct acpi_spinlock *h = (struct acpi_spinlock *)Handle; + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "acquire %s\n", al->al_name)); - if (Handle == NULL) + if (al != NULL) { + if (mtx_owned(&al->al_lock)) { + al->al_nested++; + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "acquire nested %s, depth %d\n", + al->al_name, al->al_nested)); + } else + mtx_lock_spin(&al->al_lock); + } else + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "cannot acquire null spinlock\n")); + return (0); - mtx_lock(&h->lock); - return (0); } void -AcpiOsReleaseLock (ACPI_SPINLOCK Handle, ACPI_CPU_FLAGS Flags) +AcpiOsReleaseLock(ACPI_SPINLOCK Handle, ACPI_CPU_FLAGS Flags) { - struct acpi_spinlock *h = (struct acpi_spinlock *)Handle; + struct acpi_spinlock *al = (struct acpi_spinlock *)Handle; - if (Handle == NULL) - return; - mtx_unlock(&h->lock); + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "release %s\n", al->al_name)); + + if (al != NULL) { + if (mtx_owned(&al->al_lock)) { + if (al->al_nested > 0) { + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "release nested %s, depth %d\n", + al->al_name, al->al_nested)); + al->al_nested--; + } else + mtx_unlock_spin(&al->al_lock); + } else + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "cannot release unowned %s\n", al->al_name)); + } else + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "cannot release null spinlock\n")); } -/* Section 5.2.9.1: global lock acquire/release functions */ -#define GL_ACQUIRED (-1) -#define GL_BUSY 0 -#define GL_BIT_PENDING 0x1 -#define GL_BIT_OWNED 0x2 -#define GL_BIT_MASK (GL_BIT_PENDING | GL_BIT_OWNED) +/* Section 5.2.10.1: global lock acquire/release functions */ +#define GL_ACQUIRED (-1) +#define GL_BUSY 0 +#define GL_BIT_PENDING 0x01 +#define GL_BIT_OWNED 0x02 +#define GL_BIT_MASK (GL_BIT_PENDING | GL_BIT_OWNED) /* * Acquire the global lock. If busy, set the pending bit. The caller @@ -403,7 +352,7 @@ int acpi_acquire_global_lock(uint32_t *lock) { - uint32_t new, old; + uint32_t new, old; do { old = *lock; @@ -422,7 +371,7 @@ int acpi_release_global_lock(uint32_t *lock) { - uint32_t new, old; + uint32_t new, old; do { old = *lock; --Boundary-00=_nLLAKMdakiVOZxJ--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200905051743.03520.jkim>