520 lines
13 KiB
C
520 lines
13 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (C) 2024 Loongson Technology Corporation Limited
|
||
|
*/
|
||
|
|
||
|
#include <asm/kvm_eiointc.h>
|
||
|
#include <asm/kvm_pch_pic.h>
|
||
|
#include <asm/kvm_vcpu.h>
|
||
|
#include <linux/count_zeros.h>
|
||
|
|
||
|
/* update the isr according to irq level and route irq to eiointc */
|
||
|
static void pch_pic_update_irq(struct loongarch_pch_pic *s, int irq, int level)
|
||
|
{
|
||
|
u64 mask = BIT(irq);
|
||
|
|
||
|
/*
|
||
|
* set isr and route irq to eiointc and
|
||
|
* the route table is in htmsi_vector[]
|
||
|
*/
|
||
|
if (level) {
|
||
|
if (mask & s->irr & ~s->mask) {
|
||
|
s->isr |= mask;
|
||
|
irq = s->htmsi_vector[irq];
|
||
|
eiointc_set_irq(s->kvm->arch.eiointc, irq, level);
|
||
|
}
|
||
|
} else {
|
||
|
if (mask & s->isr & ~s->irr) {
|
||
|
s->isr &= ~mask;
|
||
|
irq = s->htmsi_vector[irq];
|
||
|
eiointc_set_irq(s->kvm->arch.eiointc, irq, level);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* update batch irqs, the irq_mask is a bitmap of irqs */
|
||
|
static void pch_pic_update_batch_irqs(struct loongarch_pch_pic *s, u64 irq_mask, int level)
|
||
|
{
|
||
|
int irq, bits;
|
||
|
|
||
|
/* find each irq by irqs bitmap and update each irq */
|
||
|
bits = sizeof(irq_mask) * 8;
|
||
|
irq = find_first_bit((void *)&irq_mask, bits);
|
||
|
while (irq < bits) {
|
||
|
pch_pic_update_irq(s, irq, level);
|
||
|
bitmap_clear((void *)&irq_mask, irq, 1);
|
||
|
irq = find_first_bit((void *)&irq_mask, bits);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* called when a irq is triggered in pch pic */
|
||
|
void pch_pic_set_irq(struct loongarch_pch_pic *s, int irq, int level)
|
||
|
{
|
||
|
u64 mask = BIT(irq);
|
||
|
|
||
|
spin_lock(&s->lock);
|
||
|
if (level)
|
||
|
s->irr |= mask; /* set irr */
|
||
|
else {
|
||
|
/*
|
||
|
* In edge triggered mode, 0 does not mean to clear irq
|
||
|
* The irr register variable is cleared when cpu writes to the
|
||
|
* PCH_PIC_CLEAR_START address area
|
||
|
*/
|
||
|
if (s->edge & mask) {
|
||
|
spin_unlock(&s->lock);
|
||
|
return;
|
||
|
}
|
||
|
s->irr &= ~mask;
|
||
|
}
|
||
|
pch_pic_update_irq(s, irq, level);
|
||
|
spin_unlock(&s->lock);
|
||
|
}
|
||
|
|
||
|
/* msi irq handler */
|
||
|
void pch_msi_set_irq(struct kvm *kvm, int irq, int level)
|
||
|
{
|
||
|
eiointc_set_irq(kvm->arch.eiointc, irq, level);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* pch pic register is 64-bit, but it is accessed by 32-bit,
|
||
|
* so we use high to get whether low or high 32 bits we want
|
||
|
* to read.
|
||
|
*/
|
||
|
static u32 pch_pic_read_reg(u64 *s, int high)
|
||
|
{
|
||
|
u64 val = *s;
|
||
|
|
||
|
/* read the high 32 bits when high is 1 */
|
||
|
return high ? (u32)(val >> 32) : (u32)val;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* pch pic register is 64-bit, but it is accessed by 32-bit,
|
||
|
* so we use high to get whether low or high 32 bits we want
|
||
|
* to write.
|
||
|
*/
|
||
|
static u32 pch_pic_write_reg(u64 *s, int high, u32 v)
|
||
|
{
|
||
|
u64 val = *s, data = v;
|
||
|
|
||
|
if (high) {
|
||
|
/*
|
||
|
* Clear val high 32 bits
|
||
|
* Write the high 32 bits when the high is 1
|
||
|
*/
|
||
|
*s = (val << 32 >> 32) | (data << 32);
|
||
|
val >>= 32;
|
||
|
} else
|
||
|
/*
|
||
|
* Clear val low 32 bits
|
||
|
* Write the low 32 bits when the high is 0
|
||
|
*/
|
||
|
*s = (val >> 32 << 32) | v;
|
||
|
|
||
|
return (u32)val;
|
||
|
}
|
||
|
|
||
|
static int loongarch_pch_pic_read(struct loongarch_pch_pic *s, gpa_t addr, int len, void *val)
|
||
|
{
|
||
|
int offset, index, ret = 0;
|
||
|
u32 data = 0;
|
||
|
u64 int_id = 0;
|
||
|
|
||
|
offset = addr - s->pch_pic_base;
|
||
|
|
||
|
spin_lock(&s->lock);
|
||
|
switch (offset) {
|
||
|
case PCH_PIC_INT_ID_START ... PCH_PIC_INT_ID_END:
|
||
|
/* int id version */
|
||
|
int_id |= (u64)PCH_PIC_INT_ID_VER << 32;
|
||
|
/* irq number */
|
||
|
int_id |= (u64)31 << (32 + 16);
|
||
|
/* int id value */
|
||
|
int_id |= PCH_PIC_INT_ID_VAL;
|
||
|
*(u64 *)val = int_id;
|
||
|
break;
|
||
|
case PCH_PIC_MASK_START ... PCH_PIC_MASK_END:
|
||
|
offset -= PCH_PIC_MASK_START;
|
||
|
index = offset >> 2;
|
||
|
/* read mask reg */
|
||
|
data = pch_pic_read_reg(&s->mask, index);
|
||
|
*(u32 *)val = data;
|
||
|
break;
|
||
|
case PCH_PIC_HTMSI_EN_START ... PCH_PIC_HTMSI_EN_END:
|
||
|
offset -= PCH_PIC_HTMSI_EN_START;
|
||
|
index = offset >> 2;
|
||
|
/* read htmsi enable reg */
|
||
|
data = pch_pic_read_reg(&s->htmsi_en, index);
|
||
|
*(u32 *)val = data;
|
||
|
break;
|
||
|
case PCH_PIC_EDGE_START ... PCH_PIC_EDGE_END:
|
||
|
offset -= PCH_PIC_EDGE_START;
|
||
|
index = offset >> 2;
|
||
|
/* read edge enable reg */
|
||
|
data = pch_pic_read_reg(&s->edge, index);
|
||
|
*(u32 *)val = data;
|
||
|
break;
|
||
|
case PCH_PIC_AUTO_CTRL0_START ... PCH_PIC_AUTO_CTRL0_END:
|
||
|
case PCH_PIC_AUTO_CTRL1_START ... PCH_PIC_AUTO_CTRL1_END:
|
||
|
/* we only use default mode: fixed interrupt distribution mode */
|
||
|
*(u32 *)val = 0;
|
||
|
break;
|
||
|
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
|
||
|
/* only route to int0: eiointc */
|
||
|
*(u8 *)val = 1;
|
||
|
break;
|
||
|
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
|
||
|
offset -= PCH_PIC_HTMSI_VEC_START;
|
||
|
/* read htmsi vector */
|
||
|
data = s->htmsi_vector[offset];
|
||
|
*(u8 *)val = data;
|
||
|
break;
|
||
|
case PCH_PIC_POLARITY_START ... PCH_PIC_POLARITY_END:
|
||
|
/* we only use defalut value 0: high level triggered */
|
||
|
*(u32 *)val = 0;
|
||
|
break;
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
spin_unlock(&s->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int kvm_pch_pic_read(struct kvm_vcpu *vcpu,
|
||
|
struct kvm_io_device *dev,
|
||
|
gpa_t addr, int len, void *val)
|
||
|
{
|
||
|
int ret;
|
||
|
struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
|
||
|
|
||
|
if (!s) {
|
||
|
kvm_err("%s: pch pic irqchip not valid!\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* statistics of pch pic reading */
|
||
|
vcpu->kvm->stat.pch_pic_read_exits++;
|
||
|
ret = loongarch_pch_pic_read(s, addr, len, val);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int loongarch_pch_pic_write(struct loongarch_pch_pic *s, gpa_t addr,
|
||
|
int len, const void *val)
|
||
|
{
|
||
|
int ret;
|
||
|
u32 old, data, offset, index;
|
||
|
u64 irq;
|
||
|
|
||
|
ret = 0;
|
||
|
data = *(u32 *)val;
|
||
|
offset = addr - s->pch_pic_base;
|
||
|
|
||
|
spin_lock(&s->lock);
|
||
|
switch (offset) {
|
||
|
case PCH_PIC_MASK_START ... PCH_PIC_MASK_END:
|
||
|
offset -= PCH_PIC_MASK_START;
|
||
|
/* get whether high or low 32 bits we want to write */
|
||
|
index = offset >> 2;
|
||
|
old = pch_pic_write_reg(&s->mask, index, data);
|
||
|
/* enable irq when mask value change to 0 */
|
||
|
irq = (old & ~data) << (32 * index);
|
||
|
pch_pic_update_batch_irqs(s, irq, 1);
|
||
|
/* disable irq when mask value change to 1 */
|
||
|
irq = (~old & data) << (32 * index);
|
||
|
pch_pic_update_batch_irqs(s, irq, 0);
|
||
|
break;
|
||
|
case PCH_PIC_HTMSI_EN_START ... PCH_PIC_HTMSI_EN_END:
|
||
|
offset -= PCH_PIC_HTMSI_EN_START;
|
||
|
index = offset >> 2;
|
||
|
pch_pic_write_reg(&s->htmsi_en, index, data);
|
||
|
break;
|
||
|
case PCH_PIC_EDGE_START ... PCH_PIC_EDGE_END:
|
||
|
offset -= PCH_PIC_EDGE_START;
|
||
|
index = offset >> 2;
|
||
|
/* 1: edge triggered, 0: level triggered */
|
||
|
pch_pic_write_reg(&s->edge, index, data);
|
||
|
break;
|
||
|
case PCH_PIC_CLEAR_START ... PCH_PIC_CLEAR_END:
|
||
|
offset -= PCH_PIC_CLEAR_START;
|
||
|
index = offset >> 2;
|
||
|
/* write 1 to clear edge irq */
|
||
|
old = pch_pic_read_reg(&s->irr, index);
|
||
|
/*
|
||
|
* get the irq bitmap which is edge triggered and
|
||
|
* already set and to be cleared
|
||
|
*/
|
||
|
irq = old & pch_pic_read_reg(&s->edge, index) & data;
|
||
|
/* write irr to the new state where irqs have been cleared */
|
||
|
pch_pic_write_reg(&s->irr, index, old & ~irq);
|
||
|
/* update cleared irqs */
|
||
|
pch_pic_update_batch_irqs(s, irq, 0);
|
||
|
break;
|
||
|
case PCH_PIC_AUTO_CTRL0_START ... PCH_PIC_AUTO_CTRL0_END:
|
||
|
offset -= PCH_PIC_AUTO_CTRL0_START;
|
||
|
index = offset >> 2;
|
||
|
/* we only use default mode: fixed interrupt distribution mode */
|
||
|
pch_pic_write_reg(&s->auto_ctrl0, index, 0);
|
||
|
break;
|
||
|
case PCH_PIC_AUTO_CTRL1_START ... PCH_PIC_AUTO_CTRL1_END:
|
||
|
offset -= PCH_PIC_AUTO_CTRL1_START;
|
||
|
index = offset >> 2;
|
||
|
/* we only use default mode: fixed interrupt distribution mode */
|
||
|
pch_pic_write_reg(&s->auto_ctrl1, index, 0);
|
||
|
break;
|
||
|
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
|
||
|
offset -= PCH_PIC_ROUTE_ENTRY_START;
|
||
|
/* only route to int0: eiointc */
|
||
|
s->route_entry[offset] = 1;
|
||
|
break;
|
||
|
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
|
||
|
/* route table to eiointc */
|
||
|
offset -= PCH_PIC_HTMSI_VEC_START;
|
||
|
s->htmsi_vector[offset] = (u8)data;
|
||
|
break;
|
||
|
case PCH_PIC_POLARITY_START ... PCH_PIC_POLARITY_END:
|
||
|
offset -= PCH_PIC_POLARITY_START;
|
||
|
index = offset >> 2;
|
||
|
/* we only use defalut value 0: high level triggered */
|
||
|
pch_pic_write_reg(&s->polarity, index, 0);
|
||
|
break;
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
spin_unlock(&s->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int kvm_pch_pic_write(struct kvm_vcpu *vcpu,
|
||
|
struct kvm_io_device *dev,
|
||
|
gpa_t addr, int len, const void *val)
|
||
|
{
|
||
|
int ret;
|
||
|
struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
|
||
|
|
||
|
if (!s) {
|
||
|
kvm_err("%s: pch pic irqchip not valid!\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* statistics of pch pic writing */
|
||
|
vcpu->kvm->stat.pch_pic_write_exits++;
|
||
|
ret = loongarch_pch_pic_write(s, addr, len, val);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static const struct kvm_io_device_ops kvm_pch_pic_ops = {
|
||
|
.read = kvm_pch_pic_read,
|
||
|
.write = kvm_pch_pic_write,
|
||
|
};
|
||
|
|
||
|
static int kvm_pch_pic_init(struct kvm_device *dev, u64 addr)
|
||
|
{
|
||
|
int ret;
|
||
|
struct kvm *kvm = dev->kvm;
|
||
|
struct kvm_io_device *device;
|
||
|
struct loongarch_pch_pic *s = dev->kvm->arch.pch_pic;
|
||
|
|
||
|
s->pch_pic_base = addr;
|
||
|
device = &s->device;
|
||
|
/* init device by pch pic writing and reading ops */
|
||
|
kvm_iodevice_init(device, &kvm_pch_pic_ops);
|
||
|
mutex_lock(&kvm->slots_lock);
|
||
|
/* register pch pic device */
|
||
|
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, addr, PCH_PIC_SIZE, device);
|
||
|
mutex_unlock(&kvm->slots_lock);
|
||
|
|
||
|
return (ret < 0) ? -EFAULT : 0;
|
||
|
}
|
||
|
|
||
|
/* used by user space to get or set pch pic registers */
|
||
|
static int kvm_pch_pic_regs_access(struct kvm_device *dev,
|
||
|
struct kvm_device_attr *attr,
|
||
|
bool is_write)
|
||
|
{
|
||
|
int addr, offset, len = 8, ret = 0;
|
||
|
void __user *data;
|
||
|
void *p = NULL;
|
||
|
struct loongarch_pch_pic *s;
|
||
|
|
||
|
s = dev->kvm->arch.pch_pic;
|
||
|
addr = attr->attr;
|
||
|
data = (void __user *)attr->addr;
|
||
|
|
||
|
/* get pointer to pch pic register by addr */
|
||
|
switch (addr) {
|
||
|
case PCH_PIC_MASK_START:
|
||
|
p = &s->mask;
|
||
|
break;
|
||
|
case PCH_PIC_HTMSI_EN_START:
|
||
|
p = &s->htmsi_en;
|
||
|
break;
|
||
|
case PCH_PIC_EDGE_START:
|
||
|
p = &s->edge;
|
||
|
break;
|
||
|
case PCH_PIC_AUTO_CTRL0_START:
|
||
|
p = &s->auto_ctrl0;
|
||
|
break;
|
||
|
case PCH_PIC_AUTO_CTRL1_START:
|
||
|
p = &s->auto_ctrl1;
|
||
|
break;
|
||
|
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
|
||
|
offset = addr - PCH_PIC_ROUTE_ENTRY_START;
|
||
|
p = &s->route_entry[offset];
|
||
|
len = 1;
|
||
|
break;
|
||
|
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
|
||
|
offset = addr - PCH_PIC_HTMSI_VEC_START;
|
||
|
p = &s->htmsi_vector[offset];
|
||
|
len = 1;
|
||
|
break;
|
||
|
case PCH_PIC_INT_IRR_START:
|
||
|
p = &s->irr;
|
||
|
break;
|
||
|
case PCH_PIC_INT_ISR_START:
|
||
|
p = &s->isr;
|
||
|
break;
|
||
|
case PCH_PIC_POLARITY_START:
|
||
|
p = &s->polarity;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
spin_lock(&s->lock);
|
||
|
/* write or read value according to is_write */
|
||
|
if (is_write) {
|
||
|
if (copy_from_user(p, data, len))
|
||
|
ret = -EFAULT;
|
||
|
} else {
|
||
|
if (copy_to_user(data, p, len))
|
||
|
ret = -EFAULT;
|
||
|
}
|
||
|
spin_unlock(&s->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int kvm_pch_pic_get_attr(struct kvm_device *dev,
|
||
|
struct kvm_device_attr *attr)
|
||
|
{
|
||
|
switch (attr->group) {
|
||
|
case KVM_DEV_LOONGARCH_PCH_PIC_GRP_REGS:
|
||
|
return kvm_pch_pic_regs_access(dev, attr, false);
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int kvm_pch_pic_set_attr(struct kvm_device *dev,
|
||
|
struct kvm_device_attr *attr)
|
||
|
{
|
||
|
u64 addr;
|
||
|
void __user *uaddr = (void __user *)(long)attr->addr;
|
||
|
|
||
|
switch (attr->group) {
|
||
|
case KVM_DEV_LOONGARCH_PCH_PIC_GRP_CTRL:
|
||
|
switch (attr->attr) {
|
||
|
case KVM_DEV_LOONGARCH_PCH_PIC_CTRL_INIT:
|
||
|
if (copy_from_user(&addr, uaddr, sizeof(addr)))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (!dev->kvm->arch.pch_pic) {
|
||
|
kvm_err("%s: please create pch_pic irqchip first!\n", __func__);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
return kvm_pch_pic_init(dev, addr);
|
||
|
default:
|
||
|
kvm_err("%s: unknown group (%d) attr (%lld)\n", __func__, attr->group,
|
||
|
attr->attr);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
case KVM_DEV_LOONGARCH_PCH_PIC_GRP_REGS:
|
||
|
return kvm_pch_pic_regs_access(dev, attr, true);
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int kvm_setup_default_irq_routing(struct kvm *kvm)
|
||
|
{
|
||
|
int i, ret;
|
||
|
u32 nr = KVM_IRQCHIP_NUM_PINS;
|
||
|
struct kvm_irq_routing_entry *entries;
|
||
|
|
||
|
entries = kcalloc(nr, sizeof(*entries), GFP_KERNEL);
|
||
|
if (!entries)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
for (i = 0; i < nr; i++) {
|
||
|
entries[i].gsi = i;
|
||
|
entries[i].type = KVM_IRQ_ROUTING_IRQCHIP;
|
||
|
entries[i].u.irqchip.irqchip = 0;
|
||
|
entries[i].u.irqchip.pin = i;
|
||
|
}
|
||
|
ret = kvm_set_irq_routing(kvm, entries, nr, 0);
|
||
|
kfree(entries);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int kvm_pch_pic_create(struct kvm_device *dev, u32 type)
|
||
|
{
|
||
|
int ret;
|
||
|
struct kvm *kvm = dev->kvm;
|
||
|
struct loongarch_pch_pic *s;
|
||
|
|
||
|
/* pch pic should not has been created */
|
||
|
if (kvm->arch.pch_pic)
|
||
|
return -EINVAL;
|
||
|
|
||
|
ret = kvm_setup_default_irq_routing(kvm);
|
||
|
if (ret)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
s = kzalloc(sizeof(struct loongarch_pch_pic), GFP_KERNEL);
|
||
|
if (!s)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
spin_lock_init(&s->lock);
|
||
|
s->kvm = kvm;
|
||
|
kvm->arch.pch_pic = s;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void kvm_pch_pic_destroy(struct kvm_device *dev)
|
||
|
{
|
||
|
struct kvm *kvm;
|
||
|
struct loongarch_pch_pic *s;
|
||
|
|
||
|
if (!dev || !dev->kvm || !dev->kvm->arch.pch_pic)
|
||
|
return;
|
||
|
|
||
|
kvm = dev->kvm;
|
||
|
s = kvm->arch.pch_pic;
|
||
|
/* unregister pch pic device and free it's memory */
|
||
|
kvm_io_bus_unregister_dev(kvm, KVM_MMIO_BUS, &s->device);
|
||
|
kfree(s);
|
||
|
}
|
||
|
|
||
|
static struct kvm_device_ops kvm_pch_pic_dev_ops = {
|
||
|
.name = "kvm-loongarch-pch-pic",
|
||
|
.create = kvm_pch_pic_create,
|
||
|
.destroy = kvm_pch_pic_destroy,
|
||
|
.set_attr = kvm_pch_pic_set_attr,
|
||
|
.get_attr = kvm_pch_pic_get_attr,
|
||
|
};
|
||
|
|
||
|
int kvm_loongarch_register_pch_pic_device(void)
|
||
|
{
|
||
|
return kvm_register_device_ops(&kvm_pch_pic_dev_ops, KVM_DEV_TYPE_LOONGARCH_PCHPIC);
|
||
|
}
|