mirror of
https://github.com/torvalds/linux.git
synced 2026-03-07 23:04:33 +01:00
This was done entirely with mindless brute force, using
git grep -l '\<k[vmz]*alloc_objs*(.*, GFP_KERNEL)' |
xargs sed -i 's/\(alloc_objs*(.*\), GFP_KERNEL)/\1)/'
to convert the new alloc_obj() users that had a simple GFP_KERNEL
argument to just drop that argument.
Note that due to the extreme simplicity of the scripting, any slightly
more complex cases spread over multiple lines would not be triggered:
they definitely exist, but this covers the vast bulk of the cases, and
the resulting diff is also then easier to check automatically.
For the same reason the 'flex' versions will be done as a separate
conversion.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
489 lines
11 KiB
C
489 lines
11 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)
|
|
{
|
|
unsigned int irq;
|
|
DECLARE_BITMAP(irqs, 64) = { BITMAP_FROM_U64(irq_mask) };
|
|
|
|
for_each_set_bit(irq, irqs, 64)
|
|
pch_pic_update_irq(s, irq, level);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
static int loongarch_pch_pic_read(struct loongarch_pch_pic *s, gpa_t addr, int len, void *val)
|
|
{
|
|
int offset;
|
|
u64 data = 0;
|
|
void *ptemp;
|
|
|
|
offset = addr - s->pch_pic_base;
|
|
offset -= offset & 7;
|
|
|
|
spin_lock(&s->lock);
|
|
switch (offset) {
|
|
case PCH_PIC_INT_ID_START ... PCH_PIC_INT_ID_END:
|
|
data = s->id.data;
|
|
break;
|
|
case PCH_PIC_MASK_START ... PCH_PIC_MASK_END:
|
|
data = s->mask;
|
|
break;
|
|
case PCH_PIC_HTMSI_EN_START ... PCH_PIC_HTMSI_EN_END:
|
|
/* read htmsi enable reg */
|
|
data = s->htmsi_en;
|
|
break;
|
|
case PCH_PIC_EDGE_START ... PCH_PIC_EDGE_END:
|
|
/* read edge enable reg */
|
|
data = s->edge;
|
|
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 */
|
|
break;
|
|
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
|
|
/* only route to int0: eiointc */
|
|
ptemp = s->route_entry + (offset - PCH_PIC_ROUTE_ENTRY_START);
|
|
data = *(u64 *)ptemp;
|
|
break;
|
|
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
|
|
/* read htmsi vector */
|
|
ptemp = s->htmsi_vector + (offset - PCH_PIC_HTMSI_VEC_START);
|
|
data = *(u64 *)ptemp;
|
|
break;
|
|
case PCH_PIC_POLARITY_START ... PCH_PIC_POLARITY_END:
|
|
data = s->polarity;
|
|
break;
|
|
case PCH_PIC_INT_IRR_START:
|
|
data = s->irr;
|
|
break;
|
|
case PCH_PIC_INT_ISR_START:
|
|
data = s->isr;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
spin_unlock(&s->lock);
|
|
|
|
offset = (addr - s->pch_pic_base) & 7;
|
|
data = data >> (offset * 8);
|
|
memcpy(val, &data, len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kvm_pch_pic_read(struct kvm_vcpu *vcpu,
|
|
struct kvm_io_device *dev,
|
|
gpa_t addr, int len, void *val)
|
|
{
|
|
int ret = 0;
|
|
struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
|
|
|
|
if (!s) {
|
|
kvm_err("%s: pch pic irqchip not valid!\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
if (addr & (len - 1)) {
|
|
kvm_err("%s: pch pic not aligned addr %llx len %d\n", __func__, addr, len);
|
|
return ret;
|
|
}
|
|
|
|
/* statistics of pch pic reading */
|
|
vcpu->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 offset;
|
|
u64 old, data, mask;
|
|
void *ptemp;
|
|
|
|
switch (len) {
|
|
case 1:
|
|
data = *(u8 *)val;
|
|
mask = 0xFF;
|
|
break;
|
|
case 2:
|
|
data = *(u16 *)val;
|
|
mask = USHRT_MAX;
|
|
break;
|
|
case 4:
|
|
data = *(u32 *)val;
|
|
mask = UINT_MAX;
|
|
break;
|
|
case 8:
|
|
default:
|
|
data = *(u64 *)val;
|
|
mask = ULONG_MAX;
|
|
break;
|
|
}
|
|
|
|
offset = (addr - s->pch_pic_base) & 7;
|
|
mask = mask << (offset * 8);
|
|
data = data << (offset * 8);
|
|
offset = (addr - s->pch_pic_base) - offset;
|
|
|
|
spin_lock(&s->lock);
|
|
switch (offset) {
|
|
case PCH_PIC_MASK_START:
|
|
old = s->mask;
|
|
s->mask = (old & ~mask) | data;
|
|
if (old & ~data)
|
|
pch_pic_update_batch_irqs(s, old & ~data, 1);
|
|
if (~old & data)
|
|
pch_pic_update_batch_irqs(s, ~old & data, 0);
|
|
break;
|
|
case PCH_PIC_HTMSI_EN_START:
|
|
s->htmsi_en = (s->htmsi_en & ~mask) | data;
|
|
break;
|
|
case PCH_PIC_EDGE_START:
|
|
s->edge = (s->edge & ~mask) | data;
|
|
break;
|
|
case PCH_PIC_POLARITY_START:
|
|
s->polarity = (s->polarity & ~mask) | data;
|
|
break;
|
|
case PCH_PIC_CLEAR_START:
|
|
old = s->irr & s->edge & data;
|
|
if (old) {
|
|
s->irr &= ~old;
|
|
pch_pic_update_batch_irqs(s, old, 0);
|
|
}
|
|
break;
|
|
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
|
|
ptemp = s->htmsi_vector + (offset - PCH_PIC_HTMSI_VEC_START);
|
|
*(u64 *)ptemp = (*(u64 *)ptemp & ~mask) | data;
|
|
break;
|
|
/* Not implemented */
|
|
case PCH_PIC_AUTO_CTRL0_START:
|
|
case PCH_PIC_AUTO_CTRL1_START:
|
|
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
spin_unlock(&s->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 = 0;
|
|
struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
|
|
|
|
if (!s) {
|
|
kvm_err("%s: pch pic irqchip not valid!\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
if (addr & (len - 1)) {
|
|
kvm_err("%s: pch pic not aligned addr %llx len %d\n", __func__, addr, len);
|
|
return ret;
|
|
}
|
|
|
|
/* statistics of pch pic writing */
|
|
vcpu->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)
|
|
{
|
|
char buf[8];
|
|
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;
|
|
}
|
|
|
|
if (is_write) {
|
|
if (copy_from_user(buf, data, len))
|
|
return -EFAULT;
|
|
}
|
|
|
|
spin_lock(&s->lock);
|
|
if (is_write)
|
|
memcpy(p, buf, len);
|
|
else
|
|
memcpy(buf, p, len);
|
|
spin_unlock(&s->lock);
|
|
|
|
if (!is_write) {
|
|
if (copy_to_user(data, buf, len))
|
|
return -EFAULT;
|
|
}
|
|
|
|
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 = kzalloc_objs(*entries, nr);
|
|
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 i, ret, irq_num;
|
|
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_obj(struct loongarch_pch_pic);
|
|
if (!s)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Interrupt controller identification register 1
|
|
* Bit 24-31 Interrupt Controller ID
|
|
* Interrupt controller identification register 2
|
|
* Bit 0-7 Interrupt Controller version number
|
|
* Bit 16-23 The number of interrupt sources supported
|
|
*/
|
|
irq_num = 32;
|
|
s->mask = -1UL;
|
|
s->id.desc.id = PCH_PIC_INT_ID_VAL;
|
|
s->id.desc.version = PCH_PIC_INT_ID_VER;
|
|
s->id.desc.irq_num = irq_num - 1;
|
|
for (i = 0; i < irq_num; i++) {
|
|
s->route_entry[i] = 1;
|
|
s->htmsi_vector[i] = i;
|
|
}
|
|
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);
|
|
kfree(dev);
|
|
}
|
|
|
|
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);
|
|
}
|