2014-07-17 01:37:30 +03:00
/*
* Copyright 2014 Advanced Micro Devices , Inc .
*
* Permission is hereby granted , free of charge , to any person obtaining a
* copy of this software and associated documentation files ( the " Software " ) ,
* to deal in the Software without restriction , including without limitation
* the rights to use , copy , modify , merge , publish , distribute , sublicense ,
* and / or sell copies of the Software , and to permit persons to whom the
* Software is furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL
* THE COPYRIGHT HOLDER ( S ) OR AUTHOR ( S ) BE LIABLE FOR ANY CLAIM , DAMAGES OR
* OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE ,
* ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE .
*/
/*
* KFD Interrupts .
*
* AMD GPUs deliver interrupts by pushing an interrupt description onto the
* interrupt ring and then sending an interrupt . KGD receives the interrupt
* in ISR and sends us a pointer to each new entry on the interrupt ring .
*
* We generally can ' t process interrupt - signaled events from ISR , so we call
* out to each interrupt client module ( currently only the scheduler ) to ask if
* each interrupt is interesting . If they return true , then it requires further
* processing so we copy it to an internal interrupt ring and call each
* interrupt client again from a work - queue .
*
* There ' s no acknowledgment for the interrupts we use . The hardware simply
* queues a new interrupt each time without waiting .
*
* The fixed - size internal queue means that it ' s possible for us to lose
* interrupts because we have no back - pressure to the hardware .
*/
# include <linux/slab.h>
# include <linux/device.h>
# include "kfd_priv.h"
# define KFD_INTERRUPT_RING_SIZE 1024
static void interrupt_wq ( struct work_struct * ) ;
int kfd_interrupt_init ( struct kfd_dev * kfd )
{
void * interrupt_ring = kmalloc_array ( KFD_INTERRUPT_RING_SIZE ,
kfd - > device_info - > ih_ring_entry_size ,
GFP_KERNEL ) ;
if ( ! interrupt_ring )
return - ENOMEM ;
kfd - > interrupt_ring = interrupt_ring ;
kfd - > interrupt_ring_size =
KFD_INTERRUPT_RING_SIZE * kfd - > device_info - > ih_ring_entry_size ;
atomic_set ( & kfd - > interrupt_ring_wptr , 0 ) ;
atomic_set ( & kfd - > interrupt_ring_rptr , 0 ) ;
spin_lock_init ( & kfd - > interrupt_lock ) ;
INIT_WORK ( & kfd - > interrupt_work , interrupt_wq ) ;
kfd - > interrupts_active = true ;
/*
* After this function returns , the interrupt will be enabled . This
* barrier ensures that the interrupt running on a different processor
* sees all the above writes .
*/
smp_wmb ( ) ;
return 0 ;
}
void kfd_interrupt_exit ( struct kfd_dev * kfd )
{
/*
* Stop the interrupt handler from writing to the ring and scheduling
* workqueue items . The spinlock ensures that any interrupt running
* after we have unlocked sees interrupts_active = false .
*/
unsigned long flags ;
spin_lock_irqsave ( & kfd - > interrupt_lock , flags ) ;
kfd - > interrupts_active = false ;
spin_unlock_irqrestore ( & kfd - > interrupt_lock , flags ) ;
/*
* Flush_scheduled_work ensures that there are no outstanding
* work - queue items that will access interrupt_ring . New work items
* can ' t be created because we stopped interrupt handling above .
*/
flush_scheduled_work ( ) ;
kfree ( kfd - > interrupt_ring ) ;
}
/*
* This assumes that it can ' t be called concurrently with itself
* but only with dequeue_ih_ring_entry .
*/
bool enqueue_ih_ring_entry ( struct kfd_dev * kfd , const void * ih_ring_entry )
{
unsigned int rptr = atomic_read ( & kfd - > interrupt_ring_rptr ) ;
unsigned int wptr = atomic_read ( & kfd - > interrupt_ring_wptr ) ;
if ( ( rptr - wptr ) % kfd - > interrupt_ring_size = =
kfd - > device_info - > ih_ring_entry_size ) {
/* This is very bad, the system is likely to hang. */
dev_err_ratelimited ( kfd_chardev ( ) ,
" Interrupt ring overflow, dropping interrupt. \n " ) ;
return false ;
}
memcpy ( kfd - > interrupt_ring + wptr , ih_ring_entry ,
kfd - > device_info - > ih_ring_entry_size ) ;
wptr = ( wptr + kfd - > device_info - > ih_ring_entry_size ) %
kfd - > interrupt_ring_size ;
smp_wmb ( ) ; /* Ensure memcpy'd data is visible before wptr update. */
atomic_set ( & kfd - > interrupt_ring_wptr , wptr ) ;
return true ;
}
/*
* This assumes that it can ' t be called concurrently with itself
* but only with enqueue_ih_ring_entry .
*/
static bool dequeue_ih_ring_entry ( struct kfd_dev * kfd , void * ih_ring_entry )
{
/*
* Assume that wait queues have an implicit barrier , i . e . anything that
* happened in the ISR before it queued work is visible .
*/
unsigned int wptr = atomic_read ( & kfd - > interrupt_ring_wptr ) ;
unsigned int rptr = atomic_read ( & kfd - > interrupt_ring_rptr ) ;
if ( rptr = = wptr )
return false ;
memcpy ( ih_ring_entry , kfd - > interrupt_ring + rptr ,
kfd - > device_info - > ih_ring_entry_size ) ;
rptr = ( rptr + kfd - > device_info - > ih_ring_entry_size ) %
kfd - > interrupt_ring_size ;
/*
* Ensure the rptr write update is not visible until
* memcpy has finished reading .
*/
smp_mb ( ) ;
atomic_set ( & kfd - > interrupt_ring_rptr , rptr ) ;
return true ;
}
static void interrupt_wq ( struct work_struct * work )
{
struct kfd_dev * dev = container_of ( work , struct kfd_dev ,
interrupt_work ) ;
uint32_t ih_ring_entry [ DIV_ROUND_UP (
dev - > device_info - > ih_ring_entry_size ,
sizeof ( uint32_t ) ) ] ;
while ( dequeue_ih_ring_entry ( dev , ih_ring_entry ) )
2015-05-10 12:15:46 +03:00
dev - > device_info - > event_interrupt_class - > interrupt_wq ( dev ,
ih_ring_entry ) ;
2014-07-17 01:37:30 +03:00
}
bool interrupt_is_wanted ( struct kfd_dev * dev , const uint32_t * ih_ring_entry )
{
2015-05-10 12:15:46 +03:00
/* integer and bitwise OR so there is no boolean short-circuiting */
unsigned wanted = 0 ;
wanted | = dev - > device_info - > event_interrupt_class - > interrupt_isr ( dev ,
ih_ring_entry ) ;
return wanted ! = 0 ;
2014-07-17 01:37:30 +03:00
}