nuttx/sched/semaphore/spinlock.c
hujun5 f7843e2198 sched:remove g_cpu_schedlock g_cpu_irqsetlock g_cpu_locksetlock
we can use g_cpu_lockset to determine whether we are currently in the scheduling lock,
and all accesses and modifications to g_cpu_lockset, g_cpu_irqlock, g_cpu_irqset
are in the critical section, so we can directly operate on it.

test:
We can use qemu for testing.

compiling
make distclean -j20; ./tools/configure.sh -l qemu-armv8a:nsh_smp ;make -j20
running
qemu-system-aarch64 -cpu cortex-a53 -smp 4 -nographic -machine virt,virtualization=on,gic-version=3 -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel ./nuttx

Signed-off-by: hujun5 <hujun5@xiaomi.com>
2024-06-21 11:11:07 +09:00

561 lines
15 KiB
C

/****************************************************************************
* sched/semaphore/spinlock.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdatomic.h>
#include <sched.h>
#include <assert.h>
#include <nuttx/spinlock.h>
#include <nuttx/sched_note.h>
#include <arch/irq.h>
#if defined(CONFIG_TICKET_SPINLOCK) || defined(CONFIG_RW_SPINLOCK)
# include <stdatomic.h>
#endif
#include "sched/sched.h"
#if defined(CONFIG_SPINLOCK) || defined(CONFIG_TICKET_SPINLOCK)
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: spin_lock
*
* Description:
* If this CPU does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is non-reentrant and is prone to deadlocks in
* the case that any logic on the same CPU attempts to take the lock
* more than once.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* None. When the function returns, the spinlock was successfully locked
* by this CPU.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
void spin_lock(FAR volatile spinlock_t *lock)
{
#ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
/* Notify that we are waiting for a spinlock */
sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_LOCK);
#endif
#ifdef CONFIG_TICKET_SPINLOCK
unsigned short ticket =
atomic_fetch_add((FAR atomic_ushort *)&lock->tickets.next, 1);
while (atomic_load((FAR atomic_ushort *)&lock->tickets.owner) != ticket)
#else /* CONFIG_SPINLOCK */
while (up_testset(lock) == SP_LOCKED)
#endif
{
SP_DSB();
SP_WFE();
}
#ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
/* Notify that we have the spinlock */
sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_LOCKED);
#endif
SP_DMB();
}
/****************************************************************************
* Name: spin_lock_wo_note
*
* Description:
* If this CPU does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is the same as the above spin_lock() except that
* it does not perform instrumentation logic.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* None. When the function returns, the spinlock was successfully locked
* by this CPU.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
void spin_lock_wo_note(FAR volatile spinlock_t *lock)
{
#ifdef CONFIG_TICKET_SPINLOCK
unsigned short ticket =
atomic_fetch_add((FAR atomic_ushort *)&lock->tickets.next, 1);
while (atomic_load((FAR atomic_ushort *)&lock->tickets.owner) != ticket)
#else /* CONFIG_TICKET_SPINLOCK */
while (up_testset(lock) == SP_LOCKED)
#endif
{
SP_DSB();
SP_WFE();
}
SP_DMB();
}
/****************************************************************************
* Name: spin_trylock
*
* Description:
* Try once to lock the spinlock. Do not wait if the spinlock is already
* locked.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* false - Failure, the spinlock was already locked
* true - Success, the spinlock was successfully locked
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
bool spin_trylock(FAR volatile spinlock_t *lock)
{
#ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
/* Notify that we are waiting for a spinlock */
sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_LOCK);
#endif
#ifdef CONFIG_TICKET_SPINLOCK
unsigned short ticket =
atomic_load((FAR atomic_ushort *)&lock->tickets.next);
spinlock_t old =
{
{
ticket, ticket
}
};
spinlock_t new =
{
{
ticket, ticket + 1
}
};
if (!atomic_compare_exchange_strong((FAR atomic_uint *)&lock->value,
&old.value, new.value))
#else /* CONFIG_TICKET_SPINLOCK */
if (up_testset(lock) == SP_LOCKED)
#endif /* CONFIG_TICKET_SPINLOCK */
{
#ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
/* Notify that we abort for a spinlock */
sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_ABORT);
#endif
SP_DSB();
return false;
}
#ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
/* Notify that we have the spinlock */
sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_LOCKED);
#endif
SP_DMB();
return true;
}
/****************************************************************************
* Name: spin_trylock_wo_note
*
* Description:
* Try once to lock the spinlock. Do not wait if the spinlock is already
* locked.
*
* This implementation is the same as the above spin_trylock() except that
* it does not perform instrumentation logic.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* false - Failure, the spinlock was already locked
* true - Success, the spinlock was successfully locked
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
bool spin_trylock_wo_note(FAR volatile spinlock_t *lock)
{
#ifdef CONFIG_TICKET_SPINLOCK
unsigned short ticket =
atomic_load((FAR atomic_ushort *)&lock->tickets.next);
spinlock_t old =
{
{
ticket, ticket
}
};
spinlock_t new =
{
{
ticket, ticket + 1
}
};
if (!atomic_compare_exchange_strong((FAR atomic_uint *)&lock->value,
&old.value, new.value))
#else /* CONFIG_TICKET_SPINLOCK */
if (up_testset(lock) == SP_LOCKED)
#endif /* CONFIG_TICKET_SPINLOCK */
{
SP_DSB();
return false;
}
SP_DMB();
return true;
}
/****************************************************************************
* Name: spin_unlock
*
* Description:
* Release one count on a non-reentrant spinlock.
*
* Input Parameters:
* lock - A reference to the spinlock object to unlock.
*
* Returned Value:
* None.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
#ifdef __SP_UNLOCK_FUNCTION
void spin_unlock(FAR volatile spinlock_t *lock)
{
#ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
/* Notify that we are unlocking the spinlock */
sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_UNLOCK);
#endif
SP_DMB();
#ifdef CONFIG_TICKET_SPINLOCK
atomic_fetch_add((FAR atomic_ushort *)&lock->tickets.owner, 1);
#else
*lock = SP_UNLOCKED;
#endif
SP_DSB();
SP_SEV();
}
#endif
/****************************************************************************
* Name: spin_unlock_wo_note
*
* Description:
* Release one count on a non-reentrant spinlock.
*
* This implementation is the same as the above spin_unlock() except that
* it does not perform instrumentation logic.
*
* Input Parameters:
* lock - A reference to the spinlock object to unlock.
*
* Returned Value:
* None.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
void spin_unlock_wo_note(FAR volatile spinlock_t *lock)
{
SP_DMB();
#ifdef CONFIG_TICKET_SPINLOCK
atomic_fetch_add((FAR atomic_ushort *)&lock->tickets.owner, 1);
#else
*lock = SP_UNLOCKED;
#endif
SP_DSB();
SP_SEV();
}
#ifdef CONFIG_RW_SPINLOCK
/****************************************************************************
* Name: read_lock
*
* Description:
* If this task does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is non-reentrant and set a bit of lock.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* None. When the function returns, the spinlock was successfully locked
* by this CPU.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
void read_lock(FAR volatile rwlock_t *lock)
{
while (true)
{
int old = atomic_load((FAR atomic_int *)lock);
if (old <= RW_SP_WRITE_LOCKED)
{
DEBUGASSERT(old == RW_SP_WRITE_LOCKED);
SP_DSB();
SP_WFE();
}
else if(atomic_compare_exchange_strong((FAR atomic_int *)lock,
&old, old + 1))
{
break;
}
}
SP_DMB();
}
/****************************************************************************
* Name: read_trylock
*
* Description:
* If this task does not already hold the spinlock, then try to get the
* lock.
*
* This implementation is non-reentrant and set a bit of lock.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* false - Failure, the spinlock was already locked
* true - Success, the spinlock was successfully locked
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
bool read_trylock(FAR volatile rwlock_t *lock)
{
while (true)
{
int old = atomic_load((FAR atomic_int *)lock);
if (old <= RW_SP_WRITE_LOCKED)
{
DEBUGASSERT(old == RW_SP_WRITE_LOCKED);
return false;
}
else if (atomic_compare_exchange_strong((FAR atomic_int *)lock,
&old, old + 1))
{
break;
}
}
SP_DMB();
return true;
}
/****************************************************************************
* Name: read_unlock
*
* Description:
* Release a bit on a non-reentrant spinlock.
*
* Input Parameters:
* lock - A reference to the spinlock object to unlock.
*
* Returned Value:
* None.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
void read_unlock(FAR volatile rwlock_t *lock)
{
DEBUGASSERT(atomic_load((FAR atomic_int *)lock) >= RW_SP_READ_LOCKED);
SP_DMB();
atomic_fetch_sub((FAR atomic_int *)lock, 1);
SP_DSB();
SP_SEV();
}
/****************************************************************************
* Name: write_lock
*
* Description:
* If this task does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is non-reentrant and set all bit on lock to avoid
* readers and writers.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* None. When the function returns, the spinlock was successfully locked
* by this CPU.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
void write_lock(FAR volatile rwlock_t *lock)
{
int zero = RW_SP_UNLOCKED;
while (!atomic_compare_exchange_strong((FAR atomic_int *)lock,
&zero, RW_SP_WRITE_LOCKED))
{
SP_DSB();
SP_WFE();
}
SP_DMB();
}
/****************************************************************************
* Name: write_trylock
*
* Description:
* If this task does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is non-reentrant and set all bit on lock to avoid
* readers and writers.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* false - Failure, the spinlock was already locked
* true - Success, the spinlock was successfully locked
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
bool write_trylock(FAR volatile rwlock_t *lock)
{
int zero = RW_SP_UNLOCKED;
if (atomic_compare_exchange_strong((FAR atomic_int *)lock,
&zero, RW_SP_WRITE_LOCKED))
{
SP_DMB();
return true;
}
SP_DSB();
return false;
}
/****************************************************************************
* Name: write_unlock
*
* Description:
* Release write lock on a non-reentrant spinlock.
*
* Input Parameters:
* lock - A reference to the spinlock object to unlock.
*
* Returned Value:
* None.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/
void write_unlock(FAR volatile rwlock_t *lock)
{
/* Ensure this cpu already get write lock */
DEBUGASSERT(atomic_load((FAR atomic_int *)lock) == RW_SP_WRITE_LOCKED);
SP_DMB();
atomic_store((FAR atomic_int *)lock, RW_SP_UNLOCKED);
SP_DSB();
SP_SEV();
}
#endif /* CONFIG_RW_SPINLOCK */
#endif /* CONFIG_SPINLOCK */