* [PATCH v5 15/22] KVM: arm64: vgic-its: vgic_its_alloc_ite/device
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
Add two new helpers to allocate an its ite and an its device.
This will avoid duplication on restore path.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
Acked-by: Marc Zyngier <marc.zyngier@arm.com>
---
v4 -> v5:
- add Marc's A-b
v3 -> v4:
- fix allocation
- add comment about its_lock mutex hold
v1 -> v2:
- report itt_size fix and remove ITE_SIZE
- s/itte/ite/g
---
virt/kvm/arm/vgic/vgic-its.c | 73 ++++++++++++++++++++++++++++++--------------
1 file changed, 50 insertions(+), 23 deletions(-)
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index 55267ab..56c5123 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -742,6 +742,27 @@ static void vgic_its_free_collection(struct vgic_its *its, u32 coll_id)
kfree(collection);
}
+/* Must be called with its_lock mutex held */
+static int vgic_its_alloc_ite(struct its_device *device,
+ struct its_ite **itep,
+ struct its_collection *collection,
+ u32 lpi_id, u32 event_id)
+{
+ struct its_ite *ite;
+
+ ite = kzalloc(sizeof(*ite), GFP_KERNEL);
+ if (!ite)
+ return -ENOMEM;
+
+ ite->event_id = event_id;
+ ite->collection = collection;
+ ite->lpi = lpi_id;
+
+ list_add_tail(&ite->ite_list, &device->itt_head);
+ *itep = ite;
+ return 0;
+}
+
/*
* The MAPTI and MAPI commands map LPIs to ITTEs.
* Must be called with its_lock mutex held.
@@ -755,7 +776,7 @@ static int vgic_its_cmd_handle_mapi(struct kvm *kvm, struct vgic_its *its,
struct its_ite *ite;
struct its_device *device;
struct its_collection *collection, *new_coll = NULL;
- int lpi_nr;
+ int lpi_nr, ret;
struct vgic_irq *irq;
device = find_its_device(its, device_id);
@@ -785,19 +806,13 @@ static int vgic_its_cmd_handle_mapi(struct kvm *kvm, struct vgic_its *its,
new_coll = collection;
}
- ite = kzalloc(sizeof(struct its_ite), GFP_KERNEL);
- if (!ite) {
+ ret = vgic_its_alloc_ite(device, &ite, collection, lpi_nr, event_id);
+ if (ret) {
if (new_coll)
vgic_its_free_collection(its, coll_id);
- return -ENOMEM;
+ return ret;
}
- ite->event_id = event_id;
- list_add_tail(&ite->ite_list, &device->itt_head);
-
- ite->collection = collection;
- ite->lpi = lpi_nr;
-
irq = vgic_add_lpi(kvm, lpi_nr);
if (IS_ERR(irq)) {
if (new_coll)
@@ -836,6 +851,29 @@ static void vgic_its_unmap_device(struct kvm *kvm, struct its_device *device)
kfree(device);
}
+/* Must be called with its_lock mutex held */
+static int vgic_its_alloc_device(struct vgic_its *its,
+ struct its_device **devp,
+ u32 device_id, gpa_t itt_addr,
+ u8 nb_eventid_bits)
+{
+ struct its_device *device;
+
+ device = kzalloc(sizeof(*device), GFP_KERNEL);
+ if (!device)
+ return -ENOMEM;
+
+ device->device_id = device_id;
+ device->itt_addr = itt_addr;
+ device->nb_eventid_bits = nb_eventid_bits;
+ INIT_LIST_HEAD(&device->itt_head);
+
+ list_add_tail(&device->dev_list, &its->device_list);
+ *devp = device;
+
+ return 0;
+}
+
/*
* MAPD maps or unmaps a device ID to Interrupt Translation Tables (ITTs).
* Must be called with the its_lock mutex held.
@@ -872,19 +910,8 @@ static int vgic_its_cmd_handle_mapd(struct kvm *kvm, struct vgic_its *its,
if (!valid)
return 0;
- device = kzalloc(sizeof(struct its_device), GFP_KERNEL);
- if (!device)
- return -ENOMEM;
-
- device->device_id = device_id;
- device->nb_eventid_bits = nb_eventid_bits;
- device->itt_addr = itt_addr;
-
- INIT_LIST_HEAD(&device->itt_head);
-
- list_add_tail(&device->dev_list, &its->device_list);
-
- return 0;
+ return vgic_its_alloc_device(its, &device, device_id,
+ itt_addr, nb_eventid_bits);
}
/*
--
2.5.5
^ permalink raw reply related
* [PATCH v5 16/22] KVM: arm64: vgic-its: Add infrastructure for table lookup
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
Add a generic lookup_table() helper whose role consists in
scanning a contiguous table located in guest RAM and applying
a callback on each entry. Entries can be handled as linked lists
since the callback may return an offset to the next entry and
also tell that an entry is the last one.
Helper functions also are added to compute the device/event ID
offset to the next DTE/ITE.
compute_next_devid_offset, compute_next_eventid_offset and
lookup_table will become static in subsequent patches
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v4 -> v5:
- use kvm_read_guest
v3 -> v4:
- remove static to avoid compilation warning
- correct size computation in looup_table()
- defines now encode the number of bits used for devid and eventid offsets
- use BIT() - 1 to encode the max offets
---
virt/kvm/arm/vgic/vgic-its.c | 93 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+)
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index 56c5123..c22b35d 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -195,6 +195,8 @@ static struct its_ite *find_ite(struct vgic_its *its, u32 device_id,
#define VITS_TYPER_IDBITS 16
#define VITS_TYPER_DEVBITS 16
+#define VITS_DTE_MAX_DEVID_OFFSET (BIT(14) - 1)
+#define VITS_ITE_MAX_EVENTID_OFFSET (BIT(16) - 1)
/*
* Finds and returns a collection in the ITS collection table.
@@ -1674,6 +1676,97 @@ int vgic_its_attr_regs_access(struct kvm_device *dev,
return ret;
}
+u32 compute_next_devid_offset(struct list_head *h, struct its_device *dev)
+{
+ struct list_head *e = &dev->dev_list;
+ struct its_device *next;
+ u32 next_offset;
+
+ if (e->next == h)
+ return 0;
+ next = list_entry(e->next, struct its_device, dev_list);
+ next_offset = next->device_id - dev->device_id;
+
+ return min_t(u32, next_offset, VITS_DTE_MAX_DEVID_OFFSET);
+}
+
+u32 compute_next_eventid_offset(struct list_head *h, struct its_ite *ite)
+{
+ struct list_head *e = &ite->ite_list;
+ struct its_ite *next;
+ u32 next_offset;
+
+ if (e->next == h)
+ return 0;
+ next = list_entry(e->next, struct its_ite, ite_list);
+ next_offset = next->event_id - ite->event_id;
+
+ return min_t(u32, next_offset, VITS_ITE_MAX_EVENTID_OFFSET);
+}
+
+/**
+ * entry_fn_t - Callback called on a table entry restore path
+ * @its: its handle
+ * @id: id of the entry
+ * @entry: pointer to the entry
+ * @opaque: pointer to an opaque data
+ * @next_offset: minimal ID offset to the next entry. 0 if this
+ * entry is the last one, 1 if the entry is invalid, >= 1 if an
+ * entry's next_offset field was truly decoded
+ *
+ * Return: < 0 on error, 0 otherwise
+ */
+typedef int (*entry_fn_t)(struct vgic_its *its, u32 id, void *entry,
+ void *opaque, u32 *next_offset);
+
+/**
+ * lookup_table - scan a contiguous table in guest RAM and applies a function
+ * to each entry
+ *
+ * @its: its handle
+ * @base: base gpa of the table
+ * @size: size of the table in bytes
+ * @esz: entry size in bytes
+ * @start_id: first entry's ID
+ * @fn: function to apply on each entry
+ *
+ * Return: < 0 on error, 1 if last element identified, 0 otherwise
+ */
+int lookup_table(struct vgic_its *its, gpa_t base, int size, int esz,
+ int start_id, entry_fn_t fn, void *opaque)
+{
+ void *entry = kzalloc(esz, GFP_KERNEL);
+ struct kvm *kvm = its->dev->kvm;
+ unsigned long len = size;
+ u32 id = start_id;
+ gpa_t gpa = base;
+ int ret;
+
+ while (len > 0) {
+ u32 next_offset;
+ size_t byte_offset;
+
+ ret = kvm_read_guest(kvm, gpa, entry, esz);
+ if (ret)
+ goto out;
+
+ ret = fn(its, id, entry, opaque, &next_offset);
+ if (ret < 0 || (!ret && !next_offset))
+ goto out;
+
+ byte_offset = next_offset * esz;
+ id += next_offset;
+ gpa += byte_offset;
+ len -= byte_offset;
+ }
+ kfree(entry);
+ return 0;
+
+out:
+ kfree(entry);
+ return (ret < 0 ? ret : 1);
+}
+
/**
* vgic_its_save_device_tables - Save the device table and all ITT
* into guest RAM
--
2.5.5
^ permalink raw reply related
* [PATCH v5 17/22] KVM: arm64: vgic-its: Collection table save/restore
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
The save path copies the collection entries into guest RAM
at the GPA specified in the BASER register. This obviously
requires the BASER to be set. The last written element is a
dummy collection table entry.
We do not index by collection ID as the collection entry
can fit into 8 bytes while containing the collection ID.
On restore path we re-allocate the collection objects.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v4 -> v5:
- add macros for field encoding/decoding
- use abi->cte_esz
- rename flush into save
- check the target_addr is valid
v3 -> v4:
- replaced u64 *ptr by gpa_t gpa
- check the collection does not exist before allocating it
v1 -> v2:
- reword commit message and directly use 8 as entry size
- no kvm parameter anymore
- add helper for flush/restore cte
- table size computed here
- add le64/cpu conversions
---
virt/kvm/arm/vgic/vgic-its.c | 109 ++++++++++++++++++++++++++++++++++++++++++-
virt/kvm/arm/vgic/vgic.h | 9 ++++
2 files changed, 116 insertions(+), 2 deletions(-)
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index c22b35d..484e541 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -1785,13 +1785,97 @@ static int vgic_its_restore_device_tables(struct vgic_its *its)
return -ENXIO;
}
+static int vgic_its_save_cte(struct vgic_its *its,
+ struct its_collection *collection,
+ gpa_t gpa, int esz)
+{
+ u64 val;
+ int ret;
+
+ val = (1ULL << KVM_ITS_CTE_VALID_SHIFT |
+ ((u64)collection->target_addr << KVM_ITS_CTE_RDBASE_SHIFT) |
+ collection->collection_id);
+ val = cpu_to_le64(val);
+ ret = kvm_write_guest(its->dev->kvm, gpa, &val, esz);
+ return ret;
+}
+
+static int vgic_its_restore_cte(struct vgic_its *its, gpa_t gpa,
+ int esz, bool *valid)
+{
+ struct its_collection *collection;
+ struct kvm *kvm = its->dev->kvm;
+ u32 target_addr;
+ u32 coll_id;
+ u64 val;
+ int ret;
+
+ *valid = false;
+
+ ret = kvm_read_guest(kvm, gpa, &val, esz);
+ if (ret)
+ return ret;
+ val = le64_to_cpu(val);
+ *valid = val & KVM_ITS_CTE_VALID_MASK;
+
+ if (!*valid)
+ return 0;
+
+ target_addr = (u32)(val >> KVM_ITS_CTE_RDBASE_SHIFT);
+ coll_id = val & KVM_ITS_CTE_ICID_MASK;
+
+ if (target_addr >= atomic_read(&kvm->online_vcpus))
+ return -EINVAL;
+
+ collection = find_collection(its, coll_id);
+ if (collection)
+ return -EEXIST;
+ ret = vgic_its_alloc_collection(its, &collection, coll_id);
+ if (ret)
+ return ret;
+ collection->target_addr = target_addr;
+ return 0;
+}
+
/**
* vgic_its_save_collection_table - Save the collection table into
* guest RAM
*/
static int vgic_its_save_collection_table(struct vgic_its *its)
{
- return -ENXIO;
+ const struct vgic_its_abi *abi = vgic_its_get_abi(its);
+ struct its_collection *collection;
+ u64 val;
+ gpa_t gpa;
+ size_t max_size, filled = 0;
+ int ret, cte_esz = abi->cte_esz;
+
+ gpa = BASER_ADDRESS(its->baser_coll_table);
+ if (!gpa)
+ return 0;
+
+ max_size = GITS_BASER_NR_PAGES(its->baser_coll_table) * SZ_64K;
+
+ list_for_each_entry(collection, &its->collection_list, coll_list) {
+ if (filled == max_size)
+ return -ENOSPC;
+ ret = vgic_its_save_cte(its, collection, gpa, cte_esz);
+ if (ret)
+ return ret;
+ gpa += cte_esz;
+ filled += cte_esz;
+ }
+
+ if (filled == max_size)
+ return 0;
+
+ /*
+ * table is not fully filled, add a last dummy element
+ * with valid bit unset
+ */
+ val = 0;
+ ret = kvm_write_guest(its->dev->kvm, gpa, &val, cte_esz);
+ return ret;
}
/**
@@ -1801,7 +1885,28 @@ static int vgic_its_save_collection_table(struct vgic_its *its)
*/
static int vgic_its_restore_collection_table(struct vgic_its *its)
{
- return -ENXIO;
+ const struct vgic_its_abi *abi = vgic_its_get_abi(its);
+ size_t max_size, read = 0;
+ gpa_t gpa;
+ int ret, cte_esz = abi->cte_esz;
+
+ gpa = BASER_ADDRESS(its->baser_coll_table);
+
+ if (!gpa)
+ return 0;
+
+ max_size = GITS_BASER_NR_PAGES(its->baser_coll_table) * SZ_64K;
+
+ while (read < max_size) {
+ bool valid;
+
+ ret = vgic_its_restore_cte(its, gpa, cte_esz, &valid);
+ if (!valid || ret)
+ break;
+ gpa += cte_esz;
+ read += cte_esz;
+ }
+ return ret;
}
/**
diff --git a/virt/kvm/arm/vgic/vgic.h b/virt/kvm/arm/vgic/vgic.h
index b87f1c6..56e57c1 100644
--- a/virt/kvm/arm/vgic/vgic.h
+++ b/virt/kvm/arm/vgic/vgic.h
@@ -73,6 +73,15 @@
KVM_REG_ARM_VGIC_SYSREG_CRM_MASK | \
KVM_REG_ARM_VGIC_SYSREG_OP2_MASK)
+/*
+ * As per Documentation/virtual/kvm/devices/arm-vgic-its.txt,
+ * below macros are defined for ITS table entry encoding.
+ */
+#define KVM_ITS_CTE_VALID_SHIFT 63
+#define KVM_ITS_CTE_VALID_MASK BIT_ULL(63)
+#define KVM_ITS_CTE_RDBASE_SHIFT 16
+#define KVM_ITS_CTE_ICID_MASK GENMASK_ULL(15, 0)
+
static inline bool irq_is_pending(struct vgic_irq *irq)
{
if (irq->config == VGIC_CONFIG_EDGE)
--
2.5.5
^ permalink raw reply related
* [PATCH v5 18/22] KVM: arm64: vgic-its: vgic_its_check_id returns the entry's GPA
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
As vgic_its_check_id() computes the device/collection entry's
GPA, let's return it so that new callers can retrieve it easily.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v3 -> v4:
- check eaddr is not NULL to allow passing NULL eaddr parameter
to vgic_its_check_id
v2: new
---
virt/kvm/arm/vgic/vgic-its.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index 484e541..35b2ca1 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -645,7 +645,8 @@ static int vgic_its_cmd_handle_movi(struct kvm *kvm, struct vgic_its *its,
* is actually valid (covered by a memslot and guest accessible).
* For this we have to read the respective first level entry.
*/
-static bool vgic_its_check_id(struct vgic_its *its, u64 baser, u32 id)
+static bool vgic_its_check_id(struct vgic_its *its, u64 baser, u32 id,
+ gpa_t *eaddr)
{
int l1_tbl_size = GITS_BASER_NR_PAGES(baser) * SZ_64K;
int index;
@@ -665,6 +666,8 @@ static bool vgic_its_check_id(struct vgic_its *its, u64 baser, u32 id)
addr = BASER_ADDRESS(baser) + id * esz;
gfn = addr >> PAGE_SHIFT;
+ if (eaddr)
+ *eaddr = addr;
return kvm_is_visible_gfn(its->dev->kvm, gfn);
}
@@ -697,6 +700,8 @@ static bool vgic_its_check_id(struct vgic_its *its, u64 baser, u32 id)
indirect_ptr += index * esz;
gfn = indirect_ptr >> PAGE_SHIFT;
+ if (eaddr)
+ *eaddr = indirect_ptr;
return kvm_is_visible_gfn(its->dev->kvm, gfn);
}
@@ -706,7 +711,7 @@ static int vgic_its_alloc_collection(struct vgic_its *its,
{
struct its_collection *collection;
- if (!vgic_its_check_id(its, its->baser_coll_table, coll_id))
+ if (!vgic_its_check_id(its, its->baser_coll_table, coll_id, NULL))
return E_ITS_MAPC_COLLECTION_OOR;
collection = kzalloc(sizeof(*collection), GFP_KERNEL);
@@ -889,7 +894,7 @@ static int vgic_its_cmd_handle_mapd(struct kvm *kvm, struct vgic_its *its,
gpa_t itt_addr = its_cmd_get_ittaddr(its_cmd);
struct its_device *device;
- if (!vgic_its_check_id(its, its->baser_device_table, device_id))
+ if (!vgic_its_check_id(its, its->baser_device_table, device_id, NULL))
return E_ITS_MAPD_DEVICE_OOR;
if (valid && nb_eventid_bits > VITS_TYPER_IDBITS)
--
2.5.5
^ permalink raw reply related
* [PATCH v5 19/22] KVM: arm64: vgic-its: ITT save and restore
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
Introduce routines to save and restore device ITT and their
interrupt table entries (ITE).
The routines will be called on device table save and
restore. They will become static in subsequent patches.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v4 -> v5:
- ITE are now sorted by eventid on the flush
- rename *flush* into *save*
- use macros for shits and masks
- pass ite_esz to vgic_its_save_ite
v3 -> v4:
- lookup_table and compute_next_eventid_offset become static in this
patch
- remove static along with vgic_its_flush/restore_itt to avoid
compilation warnings
- next field only computed with a shift (mask removed)
- handle the case where the last element has not been found
v2 -> v3:
- add return 0 in vgic_its_restore_ite (was in subsequent patch)
v2: creation
---
virt/kvm/arm/vgic/vgic-its.c | 128 ++++++++++++++++++++++++++++++++++++++++++-
virt/kvm/arm/vgic/vgic.h | 4 ++
2 files changed, 129 insertions(+), 3 deletions(-)
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index 35b2ca1..b02fc3f 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -23,6 +23,7 @@
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/uaccess.h>
+#include <linux/list_sort.h>
#include <linux/irqchip/arm-gic-v3.h>
@@ -1695,7 +1696,7 @@ u32 compute_next_devid_offset(struct list_head *h, struct its_device *dev)
return min_t(u32, next_offset, VITS_DTE_MAX_DEVID_OFFSET);
}
-u32 compute_next_eventid_offset(struct list_head *h, struct its_ite *ite)
+static u32 compute_next_eventid_offset(struct list_head *h, struct its_ite *ite)
{
struct list_head *e = &ite->ite_list;
struct its_ite *next;
@@ -1737,8 +1738,8 @@ typedef int (*entry_fn_t)(struct vgic_its *its, u32 id, void *entry,
*
* Return: < 0 on error, 1 if last element identified, 0 otherwise
*/
-int lookup_table(struct vgic_its *its, gpa_t base, int size, int esz,
- int start_id, entry_fn_t fn, void *opaque)
+static int lookup_table(struct vgic_its *its, gpa_t base, int size, int esz,
+ int start_id, entry_fn_t fn, void *opaque)
{
void *entry = kzalloc(esz, GFP_KERNEL);
struct kvm *kvm = its->dev->kvm;
@@ -1773,6 +1774,127 @@ int lookup_table(struct vgic_its *its, gpa_t base, int size, int esz,
}
/**
+ * vgic_its_save_ite - Save an interrupt translation entry at @gpa
+ */
+static int vgic_its_save_ite(struct vgic_its *its, struct its_device *dev,
+ struct its_ite *ite, gpa_t gpa, int ite_esz)
+{
+ struct kvm *kvm = its->dev->kvm;
+ u32 next_offset;
+ u64 val;
+
+ next_offset = compute_next_eventid_offset(&dev->itt_head, ite);
+ val = ((u64)next_offset << KVM_ITS_ITE_NEXT_SHIFT) |
+ ((u64)ite->lpi << KVM_ITS_ITE_PINTID_SHIFT) |
+ ite->collection->collection_id;
+ val = cpu_to_le64(val);
+ return kvm_write_guest(kvm, gpa, &val, ite_esz);
+}
+
+/**
+ * vgic_its_restore_ite - restore an interrupt translation entry
+ * @event_id: id used for indexing
+ * @ptr: pointer to the ITE entry
+ * @opaque: pointer to the its_device
+ * @next: id offset to the next entry
+ */
+static int vgic_its_restore_ite(struct vgic_its *its, u32 event_id,
+ void *ptr, void *opaque, u32 *next)
+{
+ struct its_device *dev = (struct its_device *)opaque;
+ struct its_collection *collection;
+ struct kvm *kvm = its->dev->kvm;
+ u64 val, *p = (u64 *)ptr;
+ struct vgic_irq *irq;
+ u32 coll_id, lpi_id;
+ struct its_ite *ite;
+ int ret;
+
+ val = *p;
+ *next = 1;
+
+ val = le64_to_cpu(val);
+
+ coll_id = val & KVM_ITS_ITE_ICID_MASK;
+ lpi_id = (val & KVM_ITS_ITE_PINTID_MASK) >> KVM_ITS_ITE_PINTID_SHIFT;
+
+ if (!lpi_id)
+ return 0;
+
+ *next = val >> KVM_ITS_ITE_NEXT_SHIFT;
+
+ collection = find_collection(its, coll_id);
+ if (!collection)
+ return -EINVAL;
+
+ ret = vgic_its_alloc_ite(dev, &ite, collection,
+ lpi_id, event_id);
+ if (ret)
+ return ret;
+
+ irq = vgic_add_lpi(kvm, lpi_id);
+ if (IS_ERR(irq))
+ return PTR_ERR(irq);
+ ite->irq = irq;
+
+ /* restore the configuration of the LPI */
+ ret = update_lpi_config(kvm, irq, NULL);
+ if (ret)
+ return ret;
+
+ update_affinity_ite(kvm, ite);
+ return 0;
+}
+
+static int vgic_its_ite_cmp(void *priv, struct list_head *a,
+ struct list_head *b)
+{
+ struct its_ite *itea = container_of(a, struct its_ite, ite_list);
+ struct its_ite *iteb = container_of(b, struct its_ite, ite_list);
+
+ if (itea->event_id < iteb->event_id)
+ return -1;
+ else
+ return 1;
+}
+
+int vgic_its_save_itt(struct vgic_its *its, struct its_device *device)
+{
+ const struct vgic_its_abi *abi = vgic_its_get_abi(its);
+ gpa_t base = device->itt_addr;
+ struct its_ite *ite;
+ int ret, ite_esz = abi->ite_esz;
+
+ list_sort(NULL, &device->itt_head, vgic_its_ite_cmp);
+
+ list_for_each_entry(ite, &device->itt_head, ite_list) {
+ gpa_t gpa = base + ite->event_id * ite_esz;
+
+ ret = vgic_its_save_ite(its, device, ite, gpa, ite_esz);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+int vgic_its_restore_itt(struct vgic_its *its, struct its_device *dev)
+{
+ const struct vgic_its_abi *abi = vgic_its_get_abi(its);
+ gpa_t base = dev->itt_addr;
+ int ret, ite_esz = abi->ite_esz;
+ size_t max_size = BIT_ULL(dev->nb_eventid_bits) * ite_esz;
+
+ ret = lookup_table(its, base, max_size, ite_esz, 0,
+ vgic_its_restore_ite, dev);
+
+ if (ret < 0)
+ return ret;
+
+ /* if the last element has not been found we are in trouble */
+ return ret ? 0 : -EINVAL;
+}
+
+/**
* vgic_its_save_device_tables - Save the device table and all ITT
* into guest RAM
*/
diff --git a/virt/kvm/arm/vgic/vgic.h b/virt/kvm/arm/vgic/vgic.h
index 56e57c1..ce57fbd 100644
--- a/virt/kvm/arm/vgic/vgic.h
+++ b/virt/kvm/arm/vgic/vgic.h
@@ -81,6 +81,10 @@
#define KVM_ITS_CTE_VALID_MASK BIT_ULL(63)
#define KVM_ITS_CTE_RDBASE_SHIFT 16
#define KVM_ITS_CTE_ICID_MASK GENMASK_ULL(15, 0)
+#define KVM_ITS_ITE_NEXT_SHIFT 48
+#define KVM_ITS_ITE_PINTID_SHIFT 16
+#define KVM_ITS_ITE_PINTID_MASK GENMASK_ULL(47, 16)
+#define KVM_ITS_ITE_ICID_MASK GENMASK_ULL(15, 0)
static inline bool irq_is_pending(struct vgic_irq *irq)
{
--
2.5.5
^ permalink raw reply related
* [PATCH v5 20/22] KVM: arm64: vgic-its: Device table save/restore
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
This patch saves the device table entries into guest RAM.
Both flat table and 2 stage tables are supported. DeviceId
indexing is used.
For each device listed in the device table, we also save
the translation table using the vgic_its_save/restore_itt
routines.
On restore, devices are re-allocated and their ite are
re-built.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v4 -> v5:
- sort the device list by deviceid on device table save
- use defines for shifts and masks
- use abi->dte_esz
- clatify entry sizes for L1 and L2 tables
v3 -> v4:
- use the new proto for its_alloc_device
- compute_next_devid_offset, vgic_its_flush/restore_itt
become static in this patch
- change in the DTE entry format with the introduction of the
valid bit and next field width decrease; ittaddr encoded
on its full range
- fix handle_l1_entry entry handling
- correct vgic_its_table_restore error handling
v2 -> v3:
- fix itt_addr bitmask in vgic_its_restore_dte
- addition of return 0 in vgic_its_restore_ite moved to
the ITE related patch
v1 -> v2:
- use 8 byte format for DTE and ITE
- support 2 stage format
- remove kvm parameter
- ITT flush/restore moved in a separate patch
- use deviceid indexing
---
virt/kvm/arm/vgic/vgic-its.c | 183 +++++++++++++++++++++++++++++++++++++++++--
virt/kvm/arm/vgic/vgic.h | 7 ++
2 files changed, 185 insertions(+), 5 deletions(-)
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index b02fc3f..86dfc6c 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -1682,7 +1682,8 @@ int vgic_its_attr_regs_access(struct kvm_device *dev,
return ret;
}
-u32 compute_next_devid_offset(struct list_head *h, struct its_device *dev)
+static u32 compute_next_devid_offset(struct list_head *h,
+ struct its_device *dev)
{
struct list_head *e = &dev->dev_list;
struct its_device *next;
@@ -1858,7 +1859,7 @@ static int vgic_its_ite_cmp(void *priv, struct list_head *a,
return 1;
}
-int vgic_its_save_itt(struct vgic_its *its, struct its_device *device)
+static int vgic_its_save_itt(struct vgic_its *its, struct its_device *device)
{
const struct vgic_its_abi *abi = vgic_its_get_abi(its);
gpa_t base = device->itt_addr;
@@ -1877,7 +1878,7 @@ int vgic_its_save_itt(struct vgic_its *its, struct its_device *device)
return 0;
}
-int vgic_its_restore_itt(struct vgic_its *its, struct its_device *dev)
+static int vgic_its_restore_itt(struct vgic_its *its, struct its_device *dev)
{
const struct vgic_its_abi *abi = vgic_its_get_abi(its);
gpa_t base = dev->itt_addr;
@@ -1895,12 +1896,161 @@ int vgic_its_restore_itt(struct vgic_its *its, struct its_device *dev)
}
/**
+ * vgic_its_save_dte - Save a device table entry at a given GPA
+ *
+ * @its: ITS handle
+ * @dev: ITS device
+ * @ptr: GPA
+ */
+static int vgic_its_save_dte(struct vgic_its *its, struct its_device *dev,
+ gpa_t ptr, int dte_esz)
+{
+ struct kvm *kvm = its->dev->kvm;
+ u64 val, itt_addr_field;
+ int ret;
+ u32 next_offset;
+
+ itt_addr_field = dev->itt_addr >> 8;
+ next_offset = compute_next_devid_offset(&its->device_list, dev);
+ val = (1ULL << KVM_ITS_DTE_VALID_SHIFT |
+ ((u64)next_offset << KVM_ITS_DTE_NEXT_SHIFT) |
+ (itt_addr_field << KVM_ITS_DTE_ITTADDR_SHIFT) |
+ (dev->nb_eventid_bits - 1));
+ val = cpu_to_le64(val);
+ ret = kvm_write_guest(kvm, ptr, &val, dte_esz);
+ return ret;
+}
+
+/**
+ * vgic_its_restore_dte - restore a device table entry
+ *
+ * @its: its handle
+ * @id: device id the DTE corresponds to
+ * @ptr: kernel VA where the 8 byte DTE is located
+ * @opaque: unused
+ * @next: offset to the next valid device id
+ *
+ * Return: < 0 on error, 0 otherwise
+ */
+static int vgic_its_restore_dte(struct vgic_its *its, u32 id,
+ void *ptr, void *opaque, u32 *next)
+{
+ struct its_device *dev;
+ gpa_t itt_addr;
+ u8 nb_eventid_bits;
+ u64 entry = *(u64 *)ptr;
+ bool valid;
+ int ret;
+
+ entry = le64_to_cpu(entry);
+
+ valid = entry >> KVM_ITS_DTE_VALID_SHIFT;
+ nb_eventid_bits = (entry & KVM_ITS_DTE_SIZE_MASK) + 1;
+ itt_addr = ((entry & KVM_ITS_DTE_ITTADDR_MASK)
+ >> KVM_ITS_DTE_ITTADDR_SHIFT) << 8;
+ *next = 1;
+
+ if (!valid)
+ return 0;
+
+ /* dte entry is valid */
+ *next = (entry & KVM_ITS_DTE_NEXT_MASK) >> KVM_ITS_DTE_NEXT_SHIFT;
+
+ ret = vgic_its_alloc_device(its, &dev, id,
+ itt_addr, nb_eventid_bits);
+ if (ret)
+ return ret;
+ ret = vgic_its_restore_itt(its, dev);
+
+ return ret;
+}
+
+static int vgic_its_device_cmp(void *priv, struct list_head *a,
+ struct list_head *b)
+{
+ struct its_device *deva = container_of(a, struct its_device, dev_list);
+ struct its_device *devb = container_of(b, struct its_device, dev_list);
+
+ if (deva->device_id < devb->device_id)
+ return -1;
+ else
+ return 1;
+}
+
+/**
* vgic_its_save_device_tables - Save the device table and all ITT
* into guest RAM
+ *
+ * L1/L2 handling is hidden by vgic_its_check_id() helper which directly
+ * returns the GPA of the device entry
*/
static int vgic_its_save_device_tables(struct vgic_its *its)
{
- return -ENXIO;
+ const struct vgic_its_abi *abi = vgic_its_get_abi(its);
+ struct its_device *dev;
+ int dte_esz = abi->dte_esz;
+ u64 baser;
+
+ baser = its->baser_device_table;
+
+ list_sort(NULL, &its->device_list, vgic_its_device_cmp);
+
+ list_for_each_entry(dev, &its->device_list, dev_list) {
+ int ret;
+ gpa_t eaddr;
+
+ if (!vgic_its_check_id(its, baser,
+ dev->device_id, &eaddr))
+ return -EINVAL;
+
+ ret = vgic_its_save_itt(its, dev);
+ if (ret)
+ return ret;
+
+ ret = vgic_its_save_dte(its, dev, eaddr, dte_esz);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * handle_l1_entry - callback used for L1 entries (2 stage case)
+ *
+ * @its: its handle
+ * @id: id
+ * @addr: kernel VA
+ * @opaque: unused
+ * @next_offset: offset to the next L1 entry: 0 if the last element
+ * was found, 1 otherwise
+ */
+static int handle_l1_entry(struct vgic_its *its, u32 id, void *addr,
+ void *opaque, u32 *next_offset)
+{
+ const struct vgic_its_abi *abi = vgic_its_get_abi(its);
+ int l2_start_id = id * (SZ_64K / GITS_LVL1_ENTRY_SIZE);
+ u64 entry = *(u64 *)addr;
+ int ret, ite_esz = abi->ite_esz;
+ gpa_t gpa;
+
+ entry = le64_to_cpu(entry);
+ *next_offset = 1;
+
+ if (!(entry & BIT_ULL(63)))
+ return 0;
+
+ gpa = entry & GENMASK_ULL(51, 16);
+
+ ret = lookup_table(its, gpa, SZ_64K, ite_esz,
+ l2_start_id, vgic_its_restore_dte, NULL);
+
+ if (ret == 1) {
+ /* last entry was found in this L2 table */
+ *next_offset = 0;
+ ret = 0;
+ }
+
+ return ret;
}
/**
@@ -1909,7 +2059,30 @@ static int vgic_its_save_device_tables(struct vgic_its *its)
*/
static int vgic_its_restore_device_tables(struct vgic_its *its)
{
- return -ENXIO;
+ const struct vgic_its_abi *abi = vgic_its_get_abi(its);
+ u64 baser = its->baser_device_table;
+ int l1_esz, ret, l1_tbl_size = GITS_BASER_NR_PAGES(baser) * SZ_64K;
+ gpa_t l1_gpa;
+
+ l1_gpa = BASER_ADDRESS(baser);
+ if (!l1_gpa)
+ return 0;
+
+ if (baser & GITS_BASER_INDIRECT) {
+ l1_esz = 8;
+ ret = lookup_table(its, l1_gpa, l1_tbl_size, l1_esz, 0,
+ handle_l1_entry, NULL);
+ } else {
+ l1_esz = abi->dte_esz;
+ ret = lookup_table(its, l1_gpa, l1_tbl_size, l1_esz, 0,
+ vgic_its_restore_dte, NULL);
+ }
+
+ if (ret < 0)
+ return ret;
+
+ /* if last element was not found we have an issue here */
+ return ret ? 0 : -EINVAL;
}
static int vgic_its_save_cte(struct vgic_its *its,
diff --git a/virt/kvm/arm/vgic/vgic.h b/virt/kvm/arm/vgic/vgic.h
index ce57fbd..9bc52ef 100644
--- a/virt/kvm/arm/vgic/vgic.h
+++ b/virt/kvm/arm/vgic/vgic.h
@@ -85,6 +85,13 @@
#define KVM_ITS_ITE_PINTID_SHIFT 16
#define KVM_ITS_ITE_PINTID_MASK GENMASK_ULL(47, 16)
#define KVM_ITS_ITE_ICID_MASK GENMASK_ULL(15, 0)
+#define KVM_ITS_DTE_VALID_SHIFT 63
+#define KVM_ITS_DTE_VALID_MASK BIT_ULL(63)
+#define KVM_ITS_DTE_NEXT_SHIFT 49
+#define KVM_ITS_DTE_NEXT_MASK GENMASK_ULL(62, 49)
+#define KVM_ITS_DTE_ITTADDR_SHIFT 5
+#define KVM_ITS_DTE_ITTADDR_MASK GENMASK_ULL(48, 5)
+#define KVM_ITS_DTE_SIZE_MASK GENMASK_ULL(4, 0)
static inline bool irq_is_pending(struct vgic_irq *irq)
{
--
2.5.5
^ permalink raw reply related
* [PATCH v5 21/22] KVM: arm64: vgic-its: Fix pending table sync
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
In its_sync_lpi_pending_table() we currently ignore the
target_vcpu of the LPIs. We sync the pending bit found in
the vcpu pending table even if the LPI is not targeting it.
Also in vgic_its_cmd_handle_invall() we are supposed to
read the config table data for the LPIs associated to the
collection ID. At the moment we refresh all LPI config
information.
This patch passes a vpcu to vgic_copy_lpi_list() so that
this latter returns a snapshot of the LPIs targeting this
CPU and only those.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
virt/kvm/arm/vgic/vgic-its.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index 86dfc6c..be848be 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -252,13 +252,13 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,
}
/*
- * Create a snapshot of the current LPI list, so that we can enumerate all
- * LPIs without holding any lock.
- * Returns the array length and puts the kmalloc'ed array into intid_ptr.
+ * Create a snapshot of the current LPIs targeting @vcpu, so that we can
+ * enumerate those LPIs without holding any lock.
+ * Returns their number and puts the kmalloc'ed array into intid_ptr.
*/
-static int vgic_copy_lpi_list(struct kvm *kvm, u32 **intid_ptr)
+static int vgic_copy_lpi_list(struct kvm_vcpu *vcpu, u32 **intid_ptr)
{
- struct vgic_dist *dist = &kvm->arch.vgic;
+ struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
struct vgic_irq *irq;
u32 *intids;
int irq_count = dist->lpi_list_count, i = 0;
@@ -277,14 +277,14 @@ static int vgic_copy_lpi_list(struct kvm *kvm, u32 **intid_ptr)
spin_lock(&dist->lpi_list_lock);
list_for_each_entry(irq, &dist->lpi_list_head, lpi_list) {
/* We don't need to "get" the IRQ, as we hold the list lock. */
- intids[i] = irq->intid;
- if (++i == irq_count)
- break;
+ if (irq->target_vcpu != vcpu)
+ continue;
+ intids[i++] = irq->intid;
}
spin_unlock(&dist->lpi_list_lock);
*intid_ptr = intids;
- return irq_count;
+ return i;
}
/*
@@ -333,7 +333,7 @@ static u32 max_lpis_propbaser(u64 propbaser)
}
/*
- * Scan the whole LPI pending table and sync the pending bit in there
+ * Sync the pending table pending bit of LPIs targeting @vcpu
* with our own data structures. This relies on the LPI being
* mapped before.
*/
@@ -346,7 +346,7 @@ static int its_sync_lpi_pending_table(struct kvm_vcpu *vcpu)
u32 *intids;
int nr_irqs, i;
- nr_irqs = vgic_copy_lpi_list(vcpu->kvm, &intids);
+ nr_irqs = vgic_copy_lpi_list(vcpu, &intids);
if (nr_irqs < 0)
return nr_irqs;
@@ -1027,7 +1027,7 @@ static int vgic_its_cmd_handle_invall(struct kvm *kvm, struct vgic_its *its,
vcpu = kvm_get_vcpu(kvm, collection->target_addr);
- irq_count = vgic_copy_lpi_list(kvm, &intids);
+ irq_count = vgic_copy_lpi_list(vcpu, &intids);
if (irq_count < 0)
return irq_count;
--
2.5.5
^ permalink raw reply related
* [PATCH v5 22/22] KVM: arm64: vgic-v3: KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES
From: Eric Auger @ 2017-04-14 10:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164934-988-1-git-send-email-eric.auger@redhat.com>
This patch adds a new attribute to GICV3 KVM device
KVM_DEV_ARM_VGIC_GRP_CTRL group. This Allows the userspace to
flush all GICR pending tables into guest RAM. At the moment
we do not offer any restore control as the sync is implicit.
As we need the PENDBASER_ADDRESS() in vgic-v3, let's move its
definition in the irqchip header. We restore the full length
of the field, ie [51:16]. Same for PROPBASER_ADDRESS with full
field length of [51:12].
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v4 -> v5:
- move pending table save code/ctrl into VGICv3 instead of ITS
- remove useless gpa_t cast
- use the same optimization as in its_sync_lpi_pending_table
v3 -> v4:
- remove the wrong comment about locking
- pass kvm struct instead of its handle
- add comment about restore method
- remove GITR_PENDABASER.PTZ check
- continue if target_vcpu == NULL
- new locking strategy
v1 -> v2:
- do not care about the 1st KB which should be zeroed according to
the spec.
---
arch/arm/include/uapi/asm/kvm.h | 1 +
arch/arm64/include/uapi/asm/kvm.h | 1 +
include/linux/irqchip/arm-gic-v3.h | 2 ++
virt/kvm/arm/vgic/vgic-its.c | 6 ++---
virt/kvm/arm/vgic/vgic-kvm-device.c | 20 ++++++++++++++
virt/kvm/arm/vgic/vgic-v3.c | 54 +++++++++++++++++++++++++++++++++++++
virt/kvm/arm/vgic/vgic.h | 1 +
7 files changed, 81 insertions(+), 4 deletions(-)
diff --git a/arch/arm/include/uapi/asm/kvm.h b/arch/arm/include/uapi/asm/kvm.h
index 8e6563c..78fe803 100644
--- a/arch/arm/include/uapi/asm/kvm.h
+++ b/arch/arm/include/uapi/asm/kvm.h
@@ -202,6 +202,7 @@ struct kvm_arch_memory_slot {
#define KVM_DEV_ARM_VGIC_CTRL_INIT 0
#define KVM_DEV_ARM_ITS_SAVE_TABLES 1
#define KVM_DEV_ARM_ITS_RESTORE_TABLES 2
+#define KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES 3
/* KVM_IRQ_LINE irq field index values */
#define KVM_ARM_IRQ_TYPE_SHIFT 24
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 1e35115..8a3758a 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -222,6 +222,7 @@ struct kvm_arch_memory_slot {
#define KVM_DEV_ARM_VGIC_CTRL_INIT 0
#define KVM_DEV_ARM_ITS_SAVE_TABLES 1
#define KVM_DEV_ARM_ITS_RESTORE_TABLES 2
+#define KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES 3
/* Device Control API on vcpu fd */
#define KVM_ARM_VCPU_PMU_V3_CTRL 0
diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h
index 0c6798c..9d3932f 100644
--- a/include/linux/irqchip/arm-gic-v3.h
+++ b/include/linux/irqchip/arm-gic-v3.h
@@ -158,6 +158,7 @@
#define GICR_PROPBASER_RaWaWb GIC_BASER_CACHEABILITY(GICR_PROPBASER, INNER, RaWaWb)
#define GICR_PROPBASER_IDBITS_MASK (0x1f)
+#define GICR_PROPBASER_ADDRESS(x) ((x) & GENMASK_ULL(51, 12))
#define GICR_PENDBASER_SHAREABILITY_SHIFT (10)
#define GICR_PENDBASER_INNER_CACHEABILITY_SHIFT (7)
@@ -183,6 +184,7 @@
#define GICR_PENDBASER_RaWaWb GIC_BASER_CACHEABILITY(GICR_PENDBASER, INNER, RaWaWb)
#define GICR_PENDBASER_PTZ BIT_ULL(62)
+#define GICR_PENDBASER_ADDRESS(x) ((x) & GENMASK_ULL(51, 16))
/*
* Re-Distributor registers, offsets from SGI_base
diff --git a/virt/kvm/arm/vgic/vgic-its.c b/virt/kvm/arm/vgic/vgic-its.c
index be848be..745c245 100644
--- a/virt/kvm/arm/vgic/vgic-its.c
+++ b/virt/kvm/arm/vgic/vgic-its.c
@@ -189,8 +189,6 @@ static struct its_ite *find_ite(struct vgic_its *its, u32 device_id,
*/
#define BASER_ADDRESS(x) ((x) & GENMASK_ULL(47, 16))
#define CBASER_ADDRESS(x) ((x) & GENMASK_ULL(47, 12))
-#define PENDBASER_ADDRESS(x) ((x) & GENMASK_ULL(47, 16))
-#define PROPBASER_ADDRESS(x) ((x) & GENMASK_ULL(47, 12))
#define GIC_LPI_OFFSET 8192
@@ -227,7 +225,7 @@ static struct its_collection *find_collection(struct vgic_its *its, int coll_id)
static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,
struct kvm_vcpu *filter_vcpu)
{
- u64 propbase = PROPBASER_ADDRESS(kvm->arch.vgic.propbaser);
+ u64 propbase = GICR_PROPBASER_ADDRESS(kvm->arch.vgic.propbaser);
u8 prop;
int ret;
@@ -339,7 +337,7 @@ static u32 max_lpis_propbaser(u64 propbaser)
*/
static int its_sync_lpi_pending_table(struct kvm_vcpu *vcpu)
{
- gpa_t pendbase = PENDBASER_ADDRESS(vcpu->arch.vgic_cpu.pendbaser);
+ gpa_t pendbase = GICR_PENDBASER_ADDRESS(vcpu->arch.vgic_cpu.pendbaser);
struct vgic_irq *irq;
int last_byte_offset = -1;
int ret = 0;
diff --git a/virt/kvm/arm/vgic/vgic-kvm-device.c b/virt/kvm/arm/vgic/vgic-kvm-device.c
index 859bfa8..d48743c 100644
--- a/virt/kvm/arm/vgic/vgic-kvm-device.c
+++ b/virt/kvm/arm/vgic/vgic-kvm-device.c
@@ -580,6 +580,24 @@ static int vgic_v3_set_attr(struct kvm_device *dev,
reg = tmp32;
return vgic_v3_attr_regs_access(dev, attr, ®, true);
}
+ case KVM_DEV_ARM_VGIC_GRP_CTRL: {
+ int ret;
+
+ switch (attr->attr) {
+ case KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES:
+ mutex_lock(&dev->kvm->lock);
+
+ if (!lock_all_vcpus(dev->kvm)) {
+ mutex_unlock(&dev->kvm->lock);
+ return -EBUSY;
+ }
+ ret = vgic_v3_save_pending_tables(dev->kvm);
+ unlock_all_vcpus(dev->kvm);
+ mutex_unlock(&dev->kvm->lock);
+ return ret;
+ }
+ break;
+ }
}
return -ENXIO;
}
@@ -658,6 +676,8 @@ static int vgic_v3_has_attr(struct kvm_device *dev,
switch (attr->attr) {
case KVM_DEV_ARM_VGIC_CTRL_INIT:
return 0;
+ case KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES:
+ return 0;
}
}
return -ENXIO;
diff --git a/virt/kvm/arm/vgic/vgic-v3.c b/virt/kvm/arm/vgic/vgic-v3.c
index be0f4c3..1f0977f 100644
--- a/virt/kvm/arm/vgic/vgic-v3.c
+++ b/virt/kvm/arm/vgic/vgic-v3.c
@@ -15,6 +15,7 @@
#include <linux/irqchip/arm-gic-v3.h>
#include <linux/kvm.h>
#include <linux/kvm_host.h>
+//#include <linux/bitops.h>
#include <kvm/arm_vgic.h>
#include <asm/kvm_mmu.h>
#include <asm/kvm_asm.h>
@@ -252,6 +253,59 @@ void vgic_v3_enable(struct kvm_vcpu *vcpu)
vgic_v3->vgic_hcr = ICH_HCR_EN;
}
+/**
+ * vgic_its_save_pending_tables - Save the pending tables into guest RAM
+ * kvm lock and all vcpu lock must be held
+ */
+int vgic_v3_save_pending_tables(struct kvm *kvm)
+{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ int last_byte_offset = -1;
+ struct vgic_irq *irq;
+ int ret;
+
+ list_for_each_entry(irq, &dist->lpi_list_head, lpi_list) {
+ int byte_offset, bit_nr;
+ struct kvm_vcpu *vcpu;
+ gpa_t pendbase, ptr;
+ bool stored;
+ u8 val;
+
+ vcpu = irq->target_vcpu;
+ if (!vcpu)
+ continue;
+
+ pendbase = GICR_PENDBASER_ADDRESS(vcpu->arch.vgic_cpu.pendbaser);
+
+ byte_offset = irq->intid / BITS_PER_BYTE;
+ bit_nr = irq->intid % BITS_PER_BYTE;
+ ptr = pendbase + byte_offset;
+
+ if (byte_offset != last_byte_offset) {
+ ret = kvm_read_guest(kvm, ptr, &val, 1);
+ if (ret)
+ return ret;
+ last_byte_offset = byte_offset;
+ }
+
+ stored = val & bit_nr;
+ if (stored == irq->pending_latch)
+ continue;
+
+ if (irq->pending_latch)
+ val |= 1 << bit_nr;
+ else
+ val &= ~(1 << bit_nr);
+
+ ret = kvm_write_guest(kvm, ptr, &val, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+
/* check for overlapping regions and for regions crossing the end of memory */
static bool vgic_v3_check_base(struct kvm *kvm)
{
diff --git a/virt/kvm/arm/vgic/vgic.h b/virt/kvm/arm/vgic/vgic.h
index 9bc52ef..535c2fc 100644
--- a/virt/kvm/arm/vgic/vgic.h
+++ b/virt/kvm/arm/vgic/vgic.h
@@ -177,6 +177,7 @@ void vgic_v3_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
void vgic_v3_enable(struct kvm_vcpu *vcpu);
int vgic_v3_probe(const struct gic_kvm_info *info);
int vgic_v3_map_resources(struct kvm *kvm);
+int vgic_v3_save_pending_tables(struct kvm *kvm);
int vgic_register_redist_iodevs(struct kvm *kvm, gpa_t dist_base_address);
int vgic_register_its_iodevs(struct kvm *kvm);
--
2.5.5
^ permalink raw reply related
* [PATCH net-next] net: mvneta: fix failed to suspend if WOL is enabled
From: Jisheng Zhang @ 2017-04-14 11:07 UTC (permalink / raw)
To: linux-arm-kernel
Recently, suspend/resume and WOL support are added into mvneta driver.
If we enable WOL, then we get some error as below on Marvell BG4CT
platforms during suspend:
[ 184.149723] dpm_run_callback(): mdio_bus_suspend+0x0/0x50 returns -16
[ 184.149727] PM: Device f7b62004.mdio-mi:00 failed to suspend: error -16
-16 means -EBUSY, phy_suspend() will return -EBUSY if it finds the
device has WOL enabled.
We fix this issue by properly setting the netdev's power.can_wakeup
and power.wakeup, i.e
1. in mvneta_mdio_probe(), call device_set_wakeup_capable() to set
power.can_wakeup if the phy support WOL.
2. in mvneta_ethtool_set_wol(), call device_set_wakeup_enable() to
set power.wakeup if WOL has been successfully enabled in phy.
Signed-off-by: Jisheng Zhang <jszhang@marvell.com>
---
drivers/net/ethernet/marvell/mvneta.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 34a3686d2ce6..0992db47070f 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3318,6 +3318,7 @@ static void mvneta_adjust_link(struct net_device *ndev)
static int mvneta_mdio_probe(struct mvneta_port *pp)
{
struct phy_device *phy_dev;
+ struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
phy_dev = of_phy_connect(pp->dev, pp->phy_node, mvneta_adjust_link, 0,
pp->phy_interface);
@@ -3326,6 +3327,9 @@ static int mvneta_mdio_probe(struct mvneta_port *pp)
return -ENODEV;
}
+ phy_ethtool_get_wol(phy_dev, &wol);
+ device_set_wakeup_capable(&pp->dev->dev, !!wol.supported);
+
phy_dev->supported &= PHY_GBIT_FEATURES;
phy_dev->advertising = phy_dev->supported;
@@ -3942,10 +3946,16 @@ static void mvneta_ethtool_get_wol(struct net_device *dev,
static int mvneta_ethtool_set_wol(struct net_device *dev,
struct ethtool_wolinfo *wol)
{
+ int ret;
+
if (!dev->phydev)
return -EOPNOTSUPP;
- return phy_ethtool_set_wol(dev->phydev, wol);
+ ret = phy_ethtool_set_wol(dev->phydev, wol);
+ if (!ret)
+ device_set_wakeup_enable(&dev->dev, !!wol->wolopts);
+
+ return ret;
}
static const struct net_device_ops mvneta_netdev_ops = {
--
2.11.0
^ permalink raw reply related
* [PATCH v2 3/3] ARM: dts: da850: Add node for LEGO MINDSTORMS EV3 Battery
From: Sekhar Nori @ 2017-04-14 12:12 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1491944723-32174-4-git-send-email-david@lechnology.com>
Hi David,
On Wednesday 12 April 2017 02:35 AM, David Lechner wrote:
> This adds a new node to the LEGO MINDSTORMS EV3 device tree for the battery.
>
> Signed-off-by: David Lechner <david@lechnology.com>
I am done sending my pull requests for v4.12. So I added this to my
queue for v4.13
Thanks,
Sekhar
^ permalink raw reply
* [PATCH V5 2/2] thermal: broadcom: add Northstar thermal driver
From: Rafał Miłecki @ 2017-04-14 12:16 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170407044218.GA12447@localhost.localdomain>
On 04/07/2017 06:42 AM, Eduardo Valentin wrote:
> On Mon, Apr 03, 2017 at 05:48:29PM +0200, Rafa? Mi?ecki wrote:
>> From: Rafa? Mi?ecki <rafal@milecki.pl>
>>
>> Northstar is a SoC family commonly used in home routers. This commit
>> adds a driver for checking CPU temperature. As Northstar Plus seems to
>> also have this IP block this new symbol gets ARCH_BCM_IPROC dependency.
>>
>> Signed-off-by: Rafa? Mi?ecki <rafal@milecki.pl>
>> Signed-off-by: Jon Mason <jon.mason@broadcom.com>
>
> If no objection, I am applying this series.
Cool, hopefully there aren't any more objections :) Once applied should I
expect this in
https://git.kernel.org/pub/scm/linux/kernel/git/evalenti/linux-soc-thermal.git/log/?h=next
?
That would allow me to move bcm2835_thermal.c to the broadcom subdir.
>> +const struct thermal_zone_of_device_ops ns_thermal_ops = {
>
> minor correction here:
>
> -const struct thermal_zone_of_device_ops ns_thermal_ops = {
> +static const struct thermal_zone_of_device_ops ns_thermal_ops = {
>
> but I am applying this already in my tree.
>
>> + .get_temp = ns_thermal_get_temp,
>> +};
Thank you!
^ permalink raw reply
* [PATCH] drivers/of_iommu: ignore SMMU DT nodes with status 'disabled'
From: Ard Biesheuvel @ 2017-04-14 12:43 UTC (permalink / raw)
To: linux-arm-kernel
DT nodes may have a status property, and if they do, such nodes should
only be considered present if the status property is set to 'okay'.
Currently, we call the init function of IOMMUs described by the device
tree without taking this into account, which may result in the output
below on systems where some SMMUs may be legally disabled.
Failed to initialise IOMMU /smb/smmu at e0200000
Failed to initialise IOMMU /smb/smmu at e0c00000
arm-smmu e0a00000.smmu: probing hardware configuration...
arm-smmu e0a00000.smmu: SMMUv1 with:
arm-smmu e0a00000.smmu: stage 2 translation
arm-smmu e0a00000.smmu: coherent table walk
arm-smmu e0a00000.smmu: stream matching with 32 register groups, mask 0x7fff
arm-smmu e0a00000.smmu: 8 context banks (8 stage-2 only)
arm-smmu e0a00000.smmu: Supported page sizes: 0x60211000
arm-smmu e0a00000.smmu: Stage-2: 40-bit IPA -> 40-bit PA
Failed to initialise IOMMU /smb/smmu at e0600000
Failed to initialise IOMMU /smb/smmu at e0800000
Since this is not an error condition, only call the init function if
the device is enabled, which also inhibits the spurious error messages.
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
---
drivers/iommu/of_iommu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index 2683e9fc0dcf..2dd1206e6c0d 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -183,7 +183,7 @@ static int __init of_iommu_init(void)
for_each_matching_node_and_match(np, matches, &match) {
const of_iommu_init_fn init_fn = match->data;
- if (init_fn(np))
+ if (of_device_is_available(np) && init_fn(np))
pr_err("Failed to initialise IOMMU %s\n",
of_node_full_name(np));
}
--
2.9.3
^ permalink raw reply related
* [PATCH v7 1/9] drm/cma: Update DEFINE_DRM_GEM_CMA_FOPS to add get_unmapped_area
From: Neil Armstrong @ 2017-04-14 12:49 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164819-10513-2-git-send-email-yannick.fertre@st.com>
On 04/14/2017 12:13 PM, Yannick Fertre wrote:
> Missing field get_unmapped_area which is necessary with device without MMU
>
> Signed-off-by: Yannick Fertre <yannick.fertre@st.com>
> ---
> include/drm/drm_gem_cma_helper.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/include/drm/drm_gem_cma_helper.h b/include/drm/drm_gem_cma_helper.h
> index f962d33..7320b14 100644
> --- a/include/drm/drm_gem_cma_helper.h
> +++ b/include/drm/drm_gem_cma_helper.h
> @@ -50,6 +50,7 @@ struct drm_gem_cma_object {
> .read = drm_read,\
> .llseek = noop_llseek,\
> .mmap = drm_gem_cma_mmap,\
> + .get_unmapped_area = drm_gem_cma_get_unmapped_area,\
> }
>
> /* free GEM object */
>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
^ permalink raw reply
* [PATCH v7 2/9] drm/fb-cma-helper: Add drm_fb_cma_get_gem_addr()
From: Neil Armstrong @ 2017-04-14 12:54 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164819-10513-3-git-send-email-yannick.fertre@st.com>
On 04/14/2017 12:13 PM, Yannick Fertre wrote:
> Add function drm_fb_cma_get_gem_addr() which return the physical address
> of framebuffer (1st pixel). This function will usually be called by plane
> callback (atomic_update).
>
> Signed-off-by: Yannick Fertre <yannick.fertre@st.com>
> ---
> drivers/gpu/drm/drm_fb_cma_helper.c | 27 +++++++++++++++++++++++++++
> include/drm/drm_fb_cma_helper.h | 4 ++++
> 2 files changed, 31 insertions(+)
>
> diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c
> index 50abd1f..d2b77b0 100644
> --- a/drivers/gpu/drm/drm_fb_cma_helper.c
> +++ b/drivers/gpu/drm/drm_fb_cma_helper.c
> @@ -260,6 +260,33 @@ struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb,
> EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_obj);
>
> /**
> + * drm_fb_cma_get_gem_addr() - Get physical address for framebuffer
> + * @fb: The framebuffer
> + * @state: Which state of drm plane
> + * @plane: Which plane
> + * Return the CMA GEM address for given framebuffer.
> + *
> + * This function will usually be called from the PLANE callback functions.
> + */
> +dma_addr_t drm_fb_cma_get_gem_addr(struct drm_framebuffer *fb,
> + struct drm_plane_state *state,
> + unsigned int plane)
> +{
> + struct drm_fb_cma *fb_cma = to_fb_cma(fb);
> + dma_addr_t paddr;
> +
> + if (plane >= 4)
> + return 0;
Nitpick, but why not using drm_fb_cma_get_gem_obj(fb, plane) here ?
=====
struct drm_gem_cma_object *gem = drm_fb_cma_get_gem_obj(fb, plane);
if (!gem)
return 0;
paddr = gem->paddr + fb->offsets[plane];
======
> +
> + paddr = fb_cma->obj[plane]->paddr + fb->offsets[plane];
> + paddr += fb->format->cpp[plane] * (state->src_x >> 16);
> + paddr += fb->pitches[plane] * (state->src_y >> 16);
> +
> + return paddr;
> +}
> +EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_addr);
> +
> +/**
> * drm_fb_cma_prepare_fb() - Prepare CMA framebuffer
> * @plane: Which plane
> * @state: Plane state attach fence to
> diff --git a/include/drm/drm_fb_cma_helper.h b/include/drm/drm_fb_cma_helper.h
> index a5ecc0a..199a63f 100644
> --- a/include/drm/drm_fb_cma_helper.h
> +++ b/include/drm/drm_fb_cma_helper.h
> @@ -41,6 +41,10 @@ struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev,
> struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb,
> unsigned int plane);
>
> +dma_addr_t drm_fb_cma_get_gem_addr(struct drm_framebuffer *fb,
> + struct drm_plane_state *state,
> + unsigned int plane);
> +
> int drm_fb_cma_prepare_fb(struct drm_plane *plane,
> struct drm_plane_state *state);
>
>
Anyway it's still ok,
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
^ permalink raw reply
* [PATCH v7 4/9] drm/stm: Add STM32 LTDC driver
From: Neil Armstrong @ 2017-04-14 12:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164819-10513-5-git-send-email-yannick.fertre@st.com>
On 04/14/2017 12:13 PM, Yannick Fertre wrote:
> This controller provides output signals to interface directly a variety
> of LCD and TFT panels. These output signals are: RGB signals
> (up to 24bpp), vertical & horizontal synchronisations, data enable and
> the pixel clock.
>
> Reviewed-by: Eric Anholt <eric@anholt.net>
> Signed-off-by: Yannick Fertre <yannick.fertre@st.com>
> ---
> drivers/gpu/drm/Kconfig | 2 +
> drivers/gpu/drm/Makefile | 1 +
> drivers/gpu/drm/stm/Kconfig | 16 +
> drivers/gpu/drm/stm/Makefile | 7 +
> drivers/gpu/drm/stm/drv.c | 221 ++++++++
> drivers/gpu/drm/stm/ltdc.c | 1161 ++++++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/stm/ltdc.h | 40 ++
> 7 files changed, 1448 insertions(+)
> create mode 100644 drivers/gpu/drm/stm/Kconfig
> create mode 100644 drivers/gpu/drm/stm/Makefile
> create mode 100644 drivers/gpu/drm/stm/drv.c
> create mode 100644 drivers/gpu/drm/stm/ltdc.c
> create mode 100644 drivers/gpu/drm/stm/ltdc.h
>
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 78d7fc0..f57540d 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -246,6 +246,8 @@ source "drivers/gpu/drm/fsl-dcu/Kconfig"
>
> source "drivers/gpu/drm/tegra/Kconfig"
>
> +source "drivers/gpu/drm/stm/Kconfig"
> +
> source "drivers/gpu/drm/panel/Kconfig"
>
> source "drivers/gpu/drm/bridge/Kconfig"
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 59f0f9b..aa62ded 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -82,6 +82,7 @@ obj-$(CONFIG_DRM_BOCHS) += bochs/
> obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio/
> obj-$(CONFIG_DRM_MSM) += msm/
> obj-$(CONFIG_DRM_TEGRA) += tegra/
> +obj-$(CONFIG_DRM_STM) += stm/
> obj-$(CONFIG_DRM_STI) += sti/
> obj-$(CONFIG_DRM_IMX) += imx/
> obj-$(CONFIG_DRM_MEDIATEK) += mediatek/
> diff --git a/drivers/gpu/drm/stm/Kconfig b/drivers/gpu/drm/stm/Kconfig
> new file mode 100644
> index 0000000..2c4817f
> --- /dev/null
> +++ b/drivers/gpu/drm/stm/Kconfig
> @@ -0,0 +1,16 @@
> +config DRM_STM
> + tristate "DRM Support for STMicroelectronics SoC Series"
> + depends on DRM && (ARCH_STM32 || ARCH_MULTIPLATFORM)
> + select DRM_KMS_HELPER
> + select DRM_GEM_CMA_HELPER
> + select DRM_KMS_CMA_HELPER
> + select DRM_PANEL
> + select VIDEOMODE_HELPERS
> + select FB_PROVIDE_GET_FB_UNMAPPED_AREA
> + default y
> +
> + help
> + Enable support for the on-chip display controller on
> + STMicroelectronics STM32 MCUs.
> + To compile this driver as a module, choose M here: the module
> + will be called stm-drm.
> diff --git a/drivers/gpu/drm/stm/Makefile b/drivers/gpu/drm/stm/Makefile
> new file mode 100644
> index 0000000..e114d45
> --- /dev/null
> +++ b/drivers/gpu/drm/stm/Makefile
> @@ -0,0 +1,7 @@
> +ccflags-y := -Iinclude/drm
> +
> +stm-drm-y := \
> + drv.o \
> + ltdc.o
> +
> +obj-$(CONFIG_DRM_STM) += stm-drm.o
> diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c
> new file mode 100644
> index 0000000..83ab48f
> --- /dev/null
> +++ b/drivers/gpu/drm/stm/drv.c
> @@ -0,0 +1,221 @@
> +/*
> + * Copyright (C) STMicroelectronics SA 2017
> + *
> + * Authors: Philippe Cornu <philippe.cornu@st.com>
> + * Yannick Fertre <yannick.fertre@st.com>
> + * Fabien Dessenne <fabien.dessenne@st.com>
> + * Mickael Reulier <mickael.reulier@st.com>
> + *
> + * License terms: GNU General Public License (GPL), version 2
> + */
> +
> +#include <linux/component.h>
> +#include <linux/of_platform.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +
> +#include "ltdc.h"
> +
> +#define DRIVER_NAME "stm"
> +#define DRIVER_DESC "STMicroelectronics SoC DRM"
> +#define DRIVER_DATE "20170330"
> +#define DRIVER_MAJOR 1
> +#define DRIVER_MINOR 0
> +#define DRIVER_PATCH_LEVEL 0
> +
> +#define STM_MAX_FB_WIDTH 2048
> +#define STM_MAX_FB_HEIGHT 2048 /* same as width to handle orientation */
> +
> +static void drv_output_poll_changed(struct drm_device *ddev)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> +
> + drm_fbdev_cma_hotplug_event(ldev->fbdev);
> +}
> +
> +static const struct drm_mode_config_funcs drv_mode_config_funcs = {
> + .fb_create = drm_fb_cma_create,
> + .output_poll_changed = drv_output_poll_changed,
> + .atomic_check = drm_atomic_helper_check,
> + .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static void drv_lastclose(struct drm_device *ddev)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> +
> + DRM_DEBUG("%s\n", __func__);
> +
> + drm_fbdev_cma_restore_mode(ldev->fbdev);
> +}
> +
> +DEFINE_DRM_GEM_CMA_FOPS(drv_driver_fops);
> +
> +static struct drm_driver drv_driver = {
> + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME |
> + DRIVER_ATOMIC,
> + .lastclose = drv_lastclose,
> + .name = DRIVER_NAME,
> + .desc = DRIVER_DESC,
> + .date = DRIVER_DATE,
> + .major = DRIVER_MAJOR,
> + .minor = DRIVER_MINOR,
> + .patchlevel = DRIVER_PATCH_LEVEL,
> + .fops = &drv_driver_fops,
> + .dumb_create = drm_gem_cma_dumb_create,
> + .dumb_map_offset = drm_gem_cma_dumb_map_offset,
> + .dumb_destroy = drm_gem_dumb_destroy,
> + .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
> + .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> + .gem_free_object_unlocked = drm_gem_cma_free_object,
> + .gem_vm_ops = &drm_gem_cma_vm_ops,
> + .gem_prime_export = drm_gem_prime_export,
> + .gem_prime_import = drm_gem_prime_import,
> + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
> + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
> + .gem_prime_vmap = drm_gem_cma_prime_vmap,
> + .gem_prime_vunmap = drm_gem_cma_prime_vunmap,
> + .gem_prime_mmap = drm_gem_cma_prime_mmap,
> + .enable_vblank = ltdc_crtc_enable_vblank,
> + .disable_vblank = ltdc_crtc_disable_vblank,
> +};
> +
> +static int drv_load(struct drm_device *ddev)
> +{
> + struct platform_device *pdev = to_platform_device(ddev->dev);
> + struct drm_fbdev_cma *fbdev;
> + struct ltdc_device *ldev;
> + int ret;
> +
> + DRM_DEBUG("%s\n", __func__);
> +
> + ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL);
> + if (!ldev)
> + return -ENOMEM;
> +
> + ddev->dev_private = (void *)ldev;
> +
> + drm_mode_config_init(ddev);
> +
> + /*
> + * set max width and height as default value.
> + * this value would be used to check framebuffer size limitation
> + * at drm_mode_addfb().
> + */
> + ddev->mode_config.min_width = 0;
> + ddev->mode_config.min_height = 0;
> + ddev->mode_config.max_width = STM_MAX_FB_WIDTH;
> + ddev->mode_config.max_height = STM_MAX_FB_HEIGHT;
> + ddev->mode_config.funcs = &drv_mode_config_funcs;
> +
> + ret = ltdc_load(ddev);
> + if (ret)
> + goto err;
> +
> + drm_mode_config_reset(ddev);
> + drm_kms_helper_poll_init(ddev);
> +
> + if (ddev->mode_config.num_connector) {
> + ldev = ddev->dev_private;
> + fbdev = drm_fbdev_cma_init(ddev, 16,
> + ddev->mode_config.num_connector);
> + if (IS_ERR(fbdev)) {
> + DRM_DEBUG("Warning: fails to create fbdev\n");
> + fbdev = NULL;
> + }
> + ldev->fbdev = fbdev;
> + }
> +
> + platform_set_drvdata(pdev, ddev);
> +
> + return 0;
> +err:
> + drm_mode_config_cleanup(ddev);
> + return ret;
> +}
> +
> +static void drv_unload(struct drm_device *ddev)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> +
> + DRM_DEBUG("%s\n", __func__);
> +
> + if (ldev->fbdev) {
> + drm_fbdev_cma_fini(ldev->fbdev);
> + ldev->fbdev = NULL;
> + }
> + drm_kms_helper_poll_fini(ddev);
> + ltdc_unload(ddev);
> + drm_mode_config_cleanup(ddev);
> +}
> +
> +static int stm_drm_platform_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct drm_device *ddev;
> + int ret;
> +
> + DRM_DEBUG("%s\n", __func__);
> +
> + dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
> +
> + ddev = drm_dev_alloc(&drv_driver, dev);
> + if (IS_ERR(ddev))
> + return PTR_ERR(ddev);
> +
> + ret = drv_load(ddev);
> + if (ret)
> + goto err_unref;
> +
> + ret = drm_dev_register(ddev, 0);
> + if (ret)
> + goto err_unref;
> +
> + return 0;
> +
> +err_unref:
> + drm_dev_unref(ddev);
> +
> + return ret;
> +}
> +
> +static int stm_drm_platform_remove(struct platform_device *pdev)
> +{
> + struct drm_device *ddev = platform_get_drvdata(pdev);
> +
> + DRM_DEBUG("%s\n", __func__);
> +
> + drm_dev_unregister(ddev);
> + drv_unload(ddev);
> + drm_dev_unref(ddev);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id drv_dt_ids[] = {
> + { .compatible = "st,stm32-ltdc"},
> + { /* end node */ },
> +};
> +MODULE_DEVICE_TABLE(of, drv_dt_ids);
> +
> +static struct platform_driver stm_drm_platform_driver = {
> + .probe = stm_drm_platform_probe,
> + .remove = stm_drm_platform_remove,
> + .driver = {
> + .name = DRIVER_NAME,
> + .of_match_table = drv_dt_ids,
> + },
> +};
> +
> +module_platform_driver(stm_drm_platform_driver);
> +
> +MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
> +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
> +MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
> +MODULE_AUTHOR("Mickael Reulier <mickael.reulier@st.com>");
> +MODULE_DESCRIPTION("STMicroelectronics ST DRM LTDC driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
> new file mode 100644
> index 0000000..a373178
> --- /dev/null
> +++ b/drivers/gpu/drm/stm/ltdc.c
> @@ -0,0 +1,1161 @@
> +/*
> + * Copyright (C) STMicroelectronics SA 2017
> + *
> + * Authors: Philippe Cornu <philippe.cornu@st.com>
> + * Yannick Fertre <yannick.fertre@st.com>
> + * Fabien Dessenne <fabien.dessenne@st.com>
> + * Mickael Reulier <mickael.reulier@st.com>
> + *
> + * License terms: GNU General Public License (GPL), version 2
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/of_address.h>
> +#include <linux/of_graph.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_plane_helper.h>
> +
> +#include <video/videomode.h>
> +
> +#include "ltdc.h"
> +
> +#define NB_CRTC 1
> +#define CRTC_MASK GENMASK(NB_CRTC - 1, 0)
> +
> +#define MAX_IRQ 4
> +
> +#define HWVER_10200 0x010200
> +#define HWVER_10300 0x010300
> +#define HWVER_20101 0x020101
> +
> +/*
> + * The address of some registers depends on the HW version: such registers have
> + * an extra offset specified with reg_ofs.
> + */
> +#define REG_OFS_NONE 0
> +#define REG_OFS_4 4 /* Insertion of "Layer Configuration 2" reg */
> +#define REG_OFS (ldev->caps.reg_ofs)
> +#define LAY_OFS 0x80 /* Register Offset between 2 layers */
> +
> +/* Global register offsets */
> +#define LTDC_IDR 0x0000 /* IDentification */
> +#define LTDC_LCR 0x0004 /* Layer Count */
> +#define LTDC_SSCR 0x0008 /* Synchronization Size Configuration */
> +#define LTDC_BPCR 0x000C /* Back Porch Configuration */
> +#define LTDC_AWCR 0x0010 /* Active Width Configuration */
> +#define LTDC_TWCR 0x0014 /* Total Width Configuration */
> +#define LTDC_GCR 0x0018 /* Global Control */
> +#define LTDC_GC1R 0x001C /* Global Configuration 1 */
> +#define LTDC_GC2R 0x0020 /* Global Configuration 2 */
> +#define LTDC_SRCR 0x0024 /* Shadow Reload Configuration */
> +#define LTDC_GACR 0x0028 /* GAmma Correction */
> +#define LTDC_BCCR 0x002C /* Background Color Configuration */
> +#define LTDC_IER 0x0034 /* Interrupt Enable */
> +#define LTDC_ISR 0x0038 /* Interrupt Status */
> +#define LTDC_ICR 0x003C /* Interrupt Clear */
> +#define LTDC_LIPCR 0x0040 /* Line Interrupt Position Configuration */
> +#define LTDC_CPSR 0x0044 /* Current Position Status */
> +#define LTDC_CDSR 0x0048 /* Current Display Status */
> +
> +/* Layer register offsets */
> +#define LTDC_L1LC1R (0x0080) /* L1 Layer Configuration 1 */
> +#define LTDC_L1LC2R (0x0084) /* L1 Layer Configuration 2 */
> +#define LTDC_L1CR (0x0084 + REG_OFS) /* L1 Control */
> +#define LTDC_L1WHPCR (0x0088 + REG_OFS) /* L1 Window Hor Position Config */
> +#define LTDC_L1WVPCR (0x008C + REG_OFS) /* L1 Window Vert Position Config */
> +#define LTDC_L1CKCR (0x0090 + REG_OFS) /* L1 Color Keying Configuration */
> +#define LTDC_L1PFCR (0x0094 + REG_OFS) /* L1 Pixel Format Configuration */
> +#define LTDC_L1CACR (0x0098 + REG_OFS) /* L1 Constant Alpha Config */
> +#define LTDC_L1DCCR (0x009C + REG_OFS) /* L1 Default Color Configuration */
> +#define LTDC_L1BFCR (0x00A0 + REG_OFS) /* L1 Blend Factors Configuration */
> +#define LTDC_L1FBBCR (0x00A4 + REG_OFS) /* L1 FrameBuffer Bus Control */
> +#define LTDC_L1AFBCR (0x00A8 + REG_OFS) /* L1 AuxFB Control */
> +#define LTDC_L1CFBAR (0x00AC + REG_OFS) /* L1 Color FrameBuffer Address */
> +#define LTDC_L1CFBLR (0x00B0 + REG_OFS) /* L1 Color FrameBuffer Length */
> +#define LTDC_L1CFBLNR (0x00B4 + REG_OFS) /* L1 Color FrameBuffer Line Nb */
> +#define LTDC_L1AFBAR (0x00B8 + REG_OFS) /* L1 AuxFB Address */
> +#define LTDC_L1AFBLR (0x00BC + REG_OFS) /* L1 AuxFB Length */
> +#define LTDC_L1AFBLNR (0x00C0 + REG_OFS) /* L1 AuxFB Line Number */
> +#define LTDC_L1CLUTWR (0x00C4 + REG_OFS) /* L1 CLUT Write */
> +#define LTDC_L1YS1R (0x00E0 + REG_OFS) /* L1 YCbCr Scale 1 */
> +#define LTDC_L1YS2R (0x00E4 + REG_OFS) /* L1 YCbCr Scale 2 */
> +
> +/* Bit definitions */
> +#define SSCR_VSH GENMASK(10, 0) /* Vertical Synchronization Height */
> +#define SSCR_HSW GENMASK(27, 16) /* Horizontal Synchronization Width */
> +
> +#define BPCR_AVBP GENMASK(10, 0) /* Accumulated Vertical Back Porch */
> +#define BPCR_AHBP GENMASK(27, 16) /* Accumulated Horizontal Back Porch */
> +
> +#define AWCR_AAH GENMASK(10, 0) /* Accumulated Active Height */
> +#define AWCR_AAW GENMASK(27, 16) /* Accumulated Active Width */
> +
> +#define TWCR_TOTALH GENMASK(10, 0) /* TOTAL Height */
> +#define TWCR_TOTALW GENMASK(27, 16) /* TOTAL Width */
> +
> +#define GCR_LTDCEN BIT(0) /* LTDC ENable */
> +#define GCR_DEN BIT(16) /* Dither ENable */
> +#define GCR_PCPOL BIT(28) /* Pixel Clock POLarity */
> +#define GCR_DEPOL BIT(29) /* Data Enable POLarity */
> +#define GCR_VSPOL BIT(30) /* Vertical Synchro POLarity */
> +#define GCR_HSPOL BIT(31) /* Horizontal Synchro POLarity */
> +
> +#define GC1R_WBCH GENMASK(3, 0) /* Width of Blue CHannel output */
> +#define GC1R_WGCH GENMASK(7, 4) /* Width of Green Channel output */
> +#define GC1R_WRCH GENMASK(11, 8) /* Width of Red Channel output */
> +#define GC1R_PBEN BIT(12) /* Precise Blending ENable */
> +#define GC1R_DT GENMASK(15, 14) /* Dithering Technique */
> +#define GC1R_GCT GENMASK(19, 17) /* Gamma Correction Technique */
> +#define GC1R_SHREN BIT(21) /* SHadow Registers ENabled */
> +#define GC1R_BCP BIT(22) /* Background Colour Programmable */
> +#define GC1R_BBEN BIT(23) /* Background Blending ENabled */
> +#define GC1R_LNIP BIT(24) /* Line Number IRQ Position */
> +#define GC1R_TP BIT(25) /* Timing Programmable */
> +#define GC1R_IPP BIT(26) /* IRQ Polarity Programmable */
> +#define GC1R_SPP BIT(27) /* Sync Polarity Programmable */
> +#define GC1R_DWP BIT(28) /* Dither Width Programmable */
> +#define GC1R_STREN BIT(29) /* STatus Registers ENabled */
> +#define GC1R_BMEN BIT(31) /* Blind Mode ENabled */
> +
> +#define GC2R_EDCA BIT(0) /* External Display Control Ability */
> +#define GC2R_STSAEN BIT(1) /* Slave Timing Sync Ability ENabled */
> +#define GC2R_DVAEN BIT(2) /* Dual-View Ability ENabled */
> +#define GC2R_DPAEN BIT(3) /* Dual-Port Ability ENabled */
> +#define GC2R_BW GENMASK(6, 4) /* Bus Width (log2 of nb of bytes) */
> +#define GC2R_EDCEN BIT(7) /* External Display Control ENabled */
> +
> +#define SRCR_IMR BIT(0) /* IMmediate Reload */
> +#define SRCR_VBR BIT(1) /* Vertical Blanking Reload */
> +
> +#define BCCR_BCBLACK 0x00 /* Background Color BLACK */
> +#define BCCR_BCBLUE GENMASK(7, 0) /* Background Color BLUE */
> +#define BCCR_BCGREEN GENMASK(15, 8) /* Background Color GREEN */
> +#define BCCR_BCRED GENMASK(23, 16) /* Background Color RED */
> +#define BCCR_BCWHITE GENMASK(23, 0) /* Background Color WHITE */
> +
> +#define IER_LIE BIT(0) /* Line Interrupt Enable */
> +#define IER_FUIE BIT(1) /* Fifo Underrun Interrupt Enable */
> +#define IER_TERRIE BIT(2) /* Transfer ERRor Interrupt Enable */
> +#define IER_RRIE BIT(3) /* Register Reload Interrupt enable */
> +
> +#define ISR_LIF BIT(0) /* Line Interrupt Flag */
> +#define ISR_FUIF BIT(1) /* Fifo Underrun Interrupt Flag */
> +#define ISR_TERRIF BIT(2) /* Transfer ERRor Interrupt Flag */
> +#define ISR_RRIF BIT(3) /* Register Reload Interrupt Flag */
> +
> +#define LXCR_LEN BIT(0) /* Layer ENable */
> +#define LXCR_COLKEN BIT(1) /* Color Keying Enable */
> +#define LXCR_CLUTEN BIT(4) /* Color Look-Up Table ENable */
> +
> +#define LXWHPCR_WHSTPOS GENMASK(11, 0) /* Window Horizontal StarT POSition */
> +#define LXWHPCR_WHSPPOS GENMASK(27, 16) /* Window Horizontal StoP POSition */
> +
> +#define LXWVPCR_WVSTPOS GENMASK(10, 0) /* Window Vertical StarT POSition */
> +#define LXWVPCR_WVSPPOS GENMASK(26, 16) /* Window Vertical StoP POSition */
> +
> +#define LXPFCR_PF GENMASK(2, 0) /* Pixel Format */
> +
> +#define LXCACR_CONSTA GENMASK(7, 0) /* CONSTant Alpha */
> +
> +#define LXBFCR_BF2 GENMASK(2, 0) /* Blending Factor 2 */
> +#define LXBFCR_BF1 GENMASK(10, 8) /* Blending Factor 1 */
> +
> +#define LXCFBLR_CFBLL GENMASK(12, 0) /* Color Frame Buffer Line Length */
> +#define LXCFBLR_CFBP GENMASK(28, 16) /* Color Frame Buffer Pitch in bytes */
> +
> +#define LXCFBLNR_CFBLN GENMASK(10, 0) /* Color Frame Buffer Line Number */
> +
> +#define HSPOL_AL 0 /* Horizontal Sync POLarity Active Low */
> +#define VSPOL_AL 0 /* Vertical Sync POLarity Active Low */
> +#define DEPOL_AL 0 /* Data Enable POLarity Active Low */
> +#define PCPOL_IPC 0 /* Input Pixel Clock */
> +#define HSPOL_AH GCR_HSPOL /* Horizontal Sync POLarity Active High */
> +#define VSPOL_AH GCR_VSPOL /* Vertical Sync POLarity Active High */
> +#define DEPOL_AH GCR_DEPOL /* Data Enable POLarity Active High */
> +#define PCPOL_IIPC GCR_PCPOL /* Inverted Input Pixel Clock */
> +#define CONSTA_MAX 0xFF /* CONSTant Alpha MAX= 1.0 */
> +#define BF1_PAXCA 0x600 /* Pixel Alpha x Constant Alpha */
> +#define BF1_CA 0x400 /* Constant Alpha */
> +#define BF2_1PAXCA 0x007 /* 1 - (Pixel Alpha x Constant Alpha) */
> +#define BF2_1CA 0x005 /* 1 - Constant Alpha */
> +
> +#define NB_PF 8 /* Max nb of HW pixel format */
> +
> +enum ltdc_pix_fmt {
> + PF_NONE,
> + /* RGB formats */
> + PF_ARGB8888, /* ARGB [32 bits] */
> + PF_RGBA8888, /* RGBA [32 bits] */
> + PF_RGB888, /* RGB [24 bits] */
> + PF_RGB565, /* RGB [16 bits] */
> + PF_ARGB1555, /* ARGB A:1 bit RGB:15 bits [16 bits] */
> + PF_ARGB4444, /* ARGB A:4 bits R/G/B: 4 bits each [16 bits] */
> + /* Indexed formats */
> + PF_L8, /* Indexed 8 bits [8 bits] */
> + PF_AL44, /* Alpha:4 bits + indexed 4 bits [8 bits] */
> + PF_AL88 /* Alpha:8 bits + indexed 8 bits [16 bits] */
> +};
> +
> +/* The index gives the encoding of the pixel format for an HW version */
> +static const enum ltdc_pix_fmt ltdc_pix_fmt_a0[NB_PF] = {
> + PF_ARGB8888, /* 0x00 */
> + PF_RGB888, /* 0x01 */
> + PF_RGB565, /* 0x02 */
> + PF_ARGB1555, /* 0x03 */
> + PF_ARGB4444, /* 0x04 */
> + PF_L8, /* 0x05 */
> + PF_AL44, /* 0x06 */
> + PF_AL88 /* 0x07 */
> +};
> +
> +static const enum ltdc_pix_fmt ltdc_pix_fmt_a1[NB_PF] = {
> + PF_ARGB8888, /* 0x00 */
> + PF_RGB888, /* 0x01 */
> + PF_RGB565, /* 0x02 */
> + PF_RGBA8888, /* 0x03 */
> + PF_AL44, /* 0x04 */
> + PF_L8, /* 0x05 */
> + PF_ARGB1555, /* 0x06 */
> + PF_ARGB4444 /* 0x07 */
> +};
> +
> +static inline u32 reg_read(void __iomem *base, u32 reg)
> +{
> + return readl_relaxed(base + reg);
> +}
> +
> +static inline void reg_write(void __iomem *base, u32 reg, u32 val)
> +{
> + writel_relaxed(val, base + reg);
> +}
> +
> +static inline void reg_set(void __iomem *base, u32 reg, u32 mask)
> +{
> + reg_write(base, reg, reg_read(base, reg) | mask);
> +}
> +
> +static inline void reg_clear(void __iomem *base, u32 reg, u32 mask)
> +{
> + reg_write(base, reg, reg_read(base, reg) & ~mask);
> +}
> +
> +static inline void reg_update_bits(void __iomem *base, u32 reg, u32 mask,
> + u32 val)
> +{
> + reg_write(base, reg, (reg_read(base, reg) & ~mask) | val);
> +}
> +
> +static inline struct ltdc_device *crtc_to_ltdc(struct drm_crtc *crtc)
> +{
> + return (struct ltdc_device *)crtc->dev->dev_private;
> +}
> +
> +static inline struct ltdc_device *plane_to_ltdc(struct drm_plane *plane)
> +{
> + return (struct ltdc_device *)plane->dev->dev_private;
> +}
> +
> +static inline struct ltdc_device *encoder_to_ltdc(struct drm_encoder *enc)
> +{
> + return (struct ltdc_device *)enc->dev->dev_private;
> +}
> +
> +static inline struct ltdc_device *connector_to_ltdc(struct drm_connector *con)
> +{
> + return (struct ltdc_device *)con->dev->dev_private;
> +}
> +
> +static inline enum ltdc_pix_fmt to_ltdc_pixelformat(u32 drm_fmt)
> +{
> + enum ltdc_pix_fmt pf;
> +
> + switch (drm_fmt) {
> + case DRM_FORMAT_ARGB8888:
> + case DRM_FORMAT_XRGB8888:
> + pf = PF_ARGB8888;
> + break;
> + case DRM_FORMAT_RGBA8888:
> + case DRM_FORMAT_RGBX8888:
> + pf = PF_RGBA8888;
> + break;
> + case DRM_FORMAT_RGB888:
> + pf = PF_RGB888;
> + break;
> + case DRM_FORMAT_RGB565:
> + pf = PF_RGB565;
> + break;
> + case DRM_FORMAT_ARGB1555:
> + case DRM_FORMAT_XRGB1555:
> + pf = PF_ARGB1555;
> + break;
> + case DRM_FORMAT_ARGB4444:
> + case DRM_FORMAT_XRGB4444:
> + pf = PF_ARGB4444;
> + break;
> + case DRM_FORMAT_C8:
> + pf = PF_L8;
> + break;
> + default:
> + pf = PF_NONE;
> + break;
> + /* Note: There are no DRM_FORMAT for AL44 and AL88 */
> + }
> +
> + return pf;
> +}
> +
> +static inline u32 to_drm_pixelformat(enum ltdc_pix_fmt pf)
> +{
> + switch (pf) {
> + case PF_ARGB8888:
> + return DRM_FORMAT_ARGB8888;
> + case PF_RGBA8888:
> + return DRM_FORMAT_RGBA8888;
> + case PF_RGB888:
> + return DRM_FORMAT_RGB888;
> + case PF_RGB565:
> + return DRM_FORMAT_RGB565;
> + case PF_ARGB1555:
> + return DRM_FORMAT_ARGB1555;
> + case PF_ARGB4444:
> + return DRM_FORMAT_ARGB4444;
> + case PF_L8:
> + return DRM_FORMAT_C8;
> + case PF_AL44: /* No DRM support */
> + case PF_AL88: /* No DRM support */
> + case PF_NONE:
> + default:
> + return 0;
> + }
> +}
> +
> +static irqreturn_t ltdc_irq_thread(int irq, void *arg)
> +{
> + struct drm_device *ddev = arg;
> + struct ltdc_device *ldev = ddev->dev_private;
> + struct drm_crtc *crtc = drm_crtc_from_index(ddev, 0);
> +
> + /* Line IRQ : trigger the vblank event */
> + if (ldev->irq_status & ISR_LIF)
> + drm_crtc_handle_vblank(crtc);
> +
> + /* Save FIFO Underrun & Transfer Error status */
> + mutex_lock(&ldev->err_lock);
> + if (ldev->irq_status & ISR_FUIF)
> + ldev->error_status |= ISR_FUIF;
> + if (ldev->irq_status & ISR_TERRIF)
> + ldev->error_status |= ISR_TERRIF;
> + mutex_unlock(&ldev->err_lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t ltdc_irq(int irq, void *arg)
> +{
> + struct drm_device *ddev = arg;
> + struct ltdc_device *ldev = ddev->dev_private;
> +
> + /* Read & Clear the interrupt status */
> + ldev->irq_status = reg_read(ldev->regs, LTDC_ISR);
> + reg_write(ldev->regs, LTDC_ICR, ldev->irq_status);
> +
> + return IRQ_WAKE_THREAD;
> +}
> +
> +/*
> + * DRM_CRTC
> + */
> +
> +static void ltdc_crtc_load_lut(struct drm_crtc *crtc)
> +{
> + struct ltdc_device *ldev = crtc_to_ltdc(crtc);
> + unsigned int i, lay;
> +
> + for (lay = 0; lay < ldev->caps.nb_layers; lay++)
> + for (i = 0; i < 256; i++)
> + reg_write(ldev->regs, LTDC_L1CLUTWR + lay * LAY_OFS,
> + ldev->clut[i]);
> +}
> +
> +static void ltdc_crtc_enable(struct drm_crtc *crtc)
> +{
> + struct ltdc_device *ldev = crtc_to_ltdc(crtc);
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + /* Sets the background color value */
> + reg_write(ldev->regs, LTDC_BCCR, BCCR_BCBLACK);
> +
> + /* Enable IRQ */
> + reg_set(ldev->regs, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE);
> +
> + /* Immediately commit the planes */
> + reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR);
> +
> + /* Enable LTDC */
> + reg_set(ldev->regs, LTDC_GCR, GCR_LTDCEN);
> +
> + drm_crtc_vblank_on(crtc);
> +}
> +
> +static void ltdc_crtc_disable(struct drm_crtc *crtc)
> +{
> + struct ltdc_device *ldev = crtc_to_ltdc(crtc);
> + struct drm_pending_vblank_event *event = crtc->state->event;
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + drm_crtc_vblank_off(crtc);
> +
> + /* disable LTDC */
> + reg_clear(ldev->regs, LTDC_GCR, GCR_LTDCEN);
> +
> + /* disable IRQ */
> + reg_clear(ldev->regs, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE);
> +
> + /* immediately commit disable of layers before switching off LTDC */
> + reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR);
> +}
> +
> +static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> + struct ltdc_device *ldev = crtc_to_ltdc(crtc);
> + struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> + struct videomode vm;
> + int rate = mode->clock * 1000;
> + u32 hsync, vsync, accum_hbp, accum_vbp, accum_act_w, accum_act_h;
> + u32 total_width, total_height;
> + u32 val;
> +
> + drm_display_mode_to_videomode(mode, &vm);
> +
> + DRM_DEBUG_DRIVER("CRTC:%d mode:%s\n", crtc->base.id, mode->name);
> + DRM_DEBUG_DRIVER("Video mode: %dx%d", vm.hactive, vm.vactive);
> + DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
> + vm.hfront_porch, vm.hback_porch, vm.hsync_len,
> + vm.vfront_porch, vm.vback_porch, vm.vsync_len);
> +
> + /* Convert video timings to ltdc timings */
> + hsync = vm.hsync_len - 1;
> + vsync = vm.vsync_len - 1;
> + accum_hbp = hsync + vm.hback_porch;
> + accum_vbp = vsync + vm.vback_porch;
> + accum_act_w = accum_hbp + vm.hactive;
> + accum_act_h = accum_vbp + vm.vactive;
> + total_width = accum_act_w + vm.hfront_porch;
> + total_height = accum_act_h + vm.vfront_porch;
> +
> + clk_disable(ldev->pixel_clk);
> +
> + if (clk_set_rate(ldev->pixel_clk, rate) < 0) {
> + DRM_ERROR("Cannot set rate (%dHz) for pixel clk\n", rate);
> + return;
> + }
> +
> + clk_enable(ldev->pixel_clk);
> +
> + /* Configures the HS, VS, DE and PC polarities. */
> + val = HSPOL_AL | HSPOL_AL | DEPOL_AL | PCPOL_IPC;
> +
> + if (vm.flags & DISPLAY_FLAGS_HSYNC_HIGH)
> + val |= HSPOL_AH;
> +
> + if (vm.flags & DISPLAY_FLAGS_VSYNC_HIGH)
> + val |= VSPOL_AH;
> +
> + if (vm.flags & DISPLAY_FLAGS_DE_HIGH)
> + val |= DEPOL_AH;
> +
> + if (vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
> + val |= PCPOL_IIPC;
> +
> + reg_update_bits(ldev->regs, LTDC_GCR,
> + GCR_HSPOL | GCR_VSPOL | GCR_DEPOL | GCR_PCPOL, val);
> +
> + /* Set Synchronization size */
> + val = (hsync << 16) | vsync;
> + reg_update_bits(ldev->regs, LTDC_SSCR, SSCR_VSH | SSCR_HSW, val);
> +
> + /* Set Accumulated Back porch */
> + val = (accum_hbp << 16) | accum_vbp;
> + reg_update_bits(ldev->regs, LTDC_BPCR, BPCR_AVBP | BPCR_AHBP, val);
> +
> + /* Set Accumulated Active Width */
> + val = (accum_act_w << 16) | accum_act_h;
> + reg_update_bits(ldev->regs, LTDC_AWCR, AWCR_AAW | AWCR_AAH, val);
> +
> + /* Set total width & height */
> + val = (total_width << 16) | total_height;
> + reg_update_bits(ldev->regs, LTDC_TWCR, TWCR_TOTALH | TWCR_TOTALW, val);
> +
> + reg_write(ldev->regs, LTDC_LIPCR, (accum_act_h + 1));
> +}
> +
> +static void ltdc_crtc_atomic_flush(struct drm_crtc *crtc,
> + struct drm_crtc_state *old_crtc_state)
> +{
> + struct ltdc_device *ldev = crtc_to_ltdc(crtc);
> + struct drm_pending_vblank_event *event = crtc->state->event;
> +
> + DRM_DEBUG_ATOMIC("\n");
> +
> + /* Commit shadow registers = update planes at next vblank */
> + reg_set(ldev->regs, LTDC_SRCR, SRCR_VBR);
> +
> + if (event) {
> + crtc->state->event = NULL;
> +
> + spin_lock_irq(&crtc->dev->event_lock);
> + if (drm_crtc_vblank_get(crtc) == 0)
> + drm_crtc_arm_vblank_event(crtc, event);
> + else
> + drm_crtc_send_vblank_event(crtc, event);
> + spin_unlock_irq(&crtc->dev->event_lock);
> + }
> +}
> +
> +static struct drm_crtc_helper_funcs ltdc_crtc_helper_funcs = {
> + .load_lut = ltdc_crtc_load_lut,
> + .enable = ltdc_crtc_enable,
> + .disable = ltdc_crtc_disable,
> + .mode_set_nofb = ltdc_crtc_mode_set_nofb,
> + .atomic_flush = ltdc_crtc_atomic_flush,
> +};
> +
> +int ltdc_crtc_enable_vblank(struct drm_device *ddev, unsigned int pipe)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> +
> + DRM_DEBUG_DRIVER("\n");
> + reg_set(ldev->regs, LTDC_IER, IER_LIE);
> +
> + return 0;
> +}
> +
> +void ltdc_crtc_disable_vblank(struct drm_device *ddev, unsigned int pipe)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> +
> + DRM_DEBUG_DRIVER("\n");
> + reg_clear(ldev->regs, LTDC_IER, IER_LIE);
> +}
> +
> +static struct drm_crtc_funcs ltdc_crtc_funcs = {
> + .destroy = drm_crtc_cleanup,
> + .set_config = drm_atomic_helper_set_config,
> + .page_flip = drm_atomic_helper_page_flip,
> + .reset = drm_atomic_helper_crtc_reset,
> + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +/*
> + * DRM_PLANE
> + */
> +
> +static int ltdc_plane_atomic_check(struct drm_plane *plane,
> + struct drm_plane_state *state)
> +{
> + struct drm_framebuffer *fb = state->fb;
> + u32 src_x, src_y, src_w, src_h;
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + if (!fb)
> + return 0;
> +
> + /* convert src_ from 16:16 format */
> + src_x = state->src_x >> 16;
> + src_y = state->src_y >> 16;
> + src_w = state->src_w >> 16;
> + src_h = state->src_h >> 16;
> +
> + /* Reject scaling */
> + if ((src_w != state->crtc_w) || (src_h != state->crtc_h)) {
> + DRM_ERROR("Scaling is not supported");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static void ltdc_plane_atomic_update(struct drm_plane *plane,
> + struct drm_plane_state *oldstate)
> +{
> + struct ltdc_device *ldev = plane_to_ltdc(plane);
> + struct drm_plane_state *state = plane->state;
> + struct drm_framebuffer *fb = state->fb;
> + u32 lofs = plane->index * LAY_OFS;
> + u32 x0 = state->crtc_x;
> + u32 x1 = state->crtc_x + state->crtc_w - 1;
> + u32 y0 = state->crtc_y;
> + u32 y1 = state->crtc_y + state->crtc_h - 1;
> + u32 src_x, src_y, src_w, src_h;
> + u32 val, pitch_in_bytes, line_length, paddr, ahbp, avbp, bpcr;
> + enum ltdc_pix_fmt pf;
> +
> + if (!state->crtc || !fb) {
> + DRM_DEBUG_DRIVER("fb or crtc NULL");
> + return;
> + }
> +
> + /* convert src_ from 16:16 format */
> + src_x = state->src_x >> 16;
> + src_y = state->src_y >> 16;
> + src_w = state->src_w >> 16;
> + src_h = state->src_h >> 16;
> +
> + DRM_DEBUG_DRIVER(
> + "plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n",
> + plane->base.id, fb->base.id,
> + src_w, src_h, src_x, src_y,
> + state->crtc_w, state->crtc_h, state->crtc_x, state->crtc_y);
> +
> + bpcr = reg_read(ldev->regs, LTDC_BPCR);
> + ahbp = (bpcr & BPCR_AHBP) >> 16;
> + avbp = bpcr & BPCR_AVBP;
> +
> + /* Configures the horizontal start and stop position */
> + val = ((x1 + 1 + ahbp) << 16) + (x0 + 1 + ahbp);
> + reg_update_bits(ldev->regs, LTDC_L1WHPCR + lofs,
> + LXWHPCR_WHSTPOS | LXWHPCR_WHSPPOS, val);
> +
> + /* Configures the vertical start and stop position */
> + val = ((y1 + 1 + avbp) << 16) + (y0 + 1 + avbp);
> + reg_update_bits(ldev->regs, LTDC_L1WVPCR + lofs,
> + LXWVPCR_WVSTPOS | LXWVPCR_WVSPPOS, val);
> +
> + /* Specifies the pixel format */
> + pf = to_ltdc_pixelformat(fb->format->format);
> + for (val = 0; val < NB_PF; val++)
> + if (ldev->caps.pix_fmt_hw[val] == pf)
> + break;
> +
> + if (val == NB_PF) {
> + DRM_ERROR("Pixel format %.4s not supported\n",
> + (char *)&fb->format->format);
> + val = 0; /* set by default ARGB 32 bits */
> + }
> + reg_update_bits(ldev->regs, LTDC_L1PFCR + lofs, LXPFCR_PF, val);
> +
> + /* Configures the color frame buffer pitch in bytes & line length */
> + pitch_in_bytes = fb->pitches[0];
> + line_length = drm_format_plane_cpp(fb->format->format, 0) *
> + (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1;
> + val = ((pitch_in_bytes << 16) | line_length);
> + reg_update_bits(ldev->regs, LTDC_L1CFBLR + lofs,
> + LXCFBLR_CFBLL | LXCFBLR_CFBP, val);
> +
> + /* Specifies the constant alpha value */
> + val = CONSTA_MAX;
> + reg_update_bits(ldev->regs, LTDC_L1CACR + lofs,
> + LXCACR_CONSTA, val);
> +
> + /* Specifies the blending factors */
> + val = BF1_PAXCA | BF2_1PAXCA;
> + reg_update_bits(ldev->regs, LTDC_L1BFCR + lofs,
> + LXBFCR_BF2 | LXBFCR_BF1, val);
> +
> + /* Configures the frame buffer line number */
> + val = y1 - y0 + 1;
> + reg_update_bits(ldev->regs, LTDC_L1CFBLNR + lofs,
> + LXCFBLNR_CFBLN, val);
> +
> + /* Sets the FB address */
> + paddr = (u32)drm_fb_cma_get_gem_addr(fb, state, 0);
> +
> + DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr);
> + reg_write(ldev->regs, LTDC_L1CFBAR + lofs, paddr);
> +
> + /* Enable layer and CLUT if needed */
> + val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0;
> + val |= LXCR_LEN;
> + reg_update_bits(ldev->regs, LTDC_L1CR + lofs,
> + LXCR_LEN | LXCR_CLUTEN, val);
> +
> + mutex_lock(&ldev->err_lock);
> + if (ldev->error_status & ISR_FUIF) {
> + DRM_DEBUG_DRIVER("Fifo underrun\n");
> + ldev->error_status &= ~ISR_FUIF;
> + }
> + if (ldev->error_status & ISR_TERRIF) {
> + DRM_DEBUG_DRIVER("Transfer error\n");
> + ldev->error_status &= ~ISR_TERRIF;
> + }
> + mutex_unlock(&ldev->err_lock);
> +}
> +
> +static void ltdc_plane_atomic_disable(struct drm_plane *plane,
> + struct drm_plane_state *oldstate)
> +{
> + struct ltdc_device *ldev = plane_to_ltdc(plane);
> + u32 lofs = plane->index * LAY_OFS;
> +
> + /* disable layer */
> + reg_clear(ldev->regs, LTDC_L1CR + lofs, LXCR_LEN);
> +
> + DRM_DEBUG_DRIVER("CRTC:%d plane:%d\n",
> + oldstate->crtc->base.id, plane->base.id);
> +}
> +
> +static struct drm_plane_funcs ltdc_plane_funcs = {
> + .update_plane = drm_atomic_helper_update_plane,
> + .disable_plane = drm_atomic_helper_disable_plane,
> + .destroy = drm_plane_cleanup,
> + .set_property = drm_atomic_helper_plane_set_property,
> + .reset = drm_atomic_helper_plane_reset,
> + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> +};
> +
> +static const struct drm_plane_helper_funcs ltdc_plane_helper_funcs = {
> + .atomic_check = ltdc_plane_atomic_check,
> + .atomic_update = ltdc_plane_atomic_update,
> + .atomic_disable = ltdc_plane_atomic_disable,
> +};
> +
> +static struct drm_plane *ltdc_plane_create(struct drm_device *ddev,
> + enum drm_plane_type type)
> +{
> + unsigned long possible_crtcs = CRTC_MASK;
> + struct ltdc_device *ldev = ddev->dev_private;
> + struct device *dev = ddev->dev;
> + struct drm_plane *plane;
> + unsigned int i, nb_fmt = 0;
> + u32 formats[NB_PF];
> + u32 drm_fmt;
> + int ret;
> +
> + /* Get supported pixel formats */
> + for (i = 0; i < NB_PF; i++) {
> + drm_fmt = to_drm_pixelformat(ldev->caps.pix_fmt_hw[i]);
> + if (!drm_fmt)
> + continue;
> + formats[nb_fmt++] = drm_fmt;
> + }
> +
> + plane = devm_kzalloc(dev, sizeof(*plane), GFP_KERNEL);
> + if (!plane)
> + return 0;
> +
> + ret = drm_universal_plane_init(ddev, plane, possible_crtcs,
> + <dc_plane_funcs, formats, nb_fmt,
> + type, NULL);
> + if (ret < 0)
> + return 0;
> +
> + drm_plane_helper_add(plane, <dc_plane_helper_funcs);
> +
> + DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id);
> +
> + return plane;
> +}
> +
> +static void ltdc_plane_destroy_all(struct drm_device *ddev)
> +{
> + struct drm_plane *plane, *plane_temp;
> +
> + list_for_each_entry_safe(plane, plane_temp,
> + &ddev->mode_config.plane_list, head)
> + drm_plane_cleanup(plane);
> +}
> +
> +static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> + struct drm_plane *primary, *overlay;
> + unsigned int i;
> + int res;
> +
> + primary = ltdc_plane_create(ddev, DRM_PLANE_TYPE_PRIMARY);
> + if (!primary) {
> + DRM_ERROR("Can not create primary plane\n");
> + return -EINVAL;
> + }
> +
> + res = drm_crtc_init_with_planes(ddev, crtc, primary, NULL,
> + <dc_crtc_funcs, NULL);
> + if (res) {
> + DRM_ERROR("Can not initialize CRTC\n");
> + goto cleanup;
> + }
> +
> + drm_crtc_helper_add(crtc, <dc_crtc_helper_funcs);
> +
> + DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id);
> +
> + /* Add planes. Note : the first layer is used by primary plane */
> + for (i = 1; i < ldev->caps.nb_layers; i++) {
> + overlay = ltdc_plane_create(ddev, DRM_PLANE_TYPE_OVERLAY);
> + if (!overlay) {
> + res = -ENOMEM;
> + DRM_ERROR("Can not create overlay plane %d\n", i);
> + goto cleanup;
> + }
> + }
> +
> + return 0;
> +
> +cleanup:
> + ltdc_plane_destroy_all(ddev);
> + return res;
> +}
> +
> +/*
> + * DRM_ENCODER
> + */
> +
> +static void ltdc_rgb_encoder_enable(struct drm_encoder *encoder)
> +{
> + struct ltdc_device *ldev = encoder_to_ltdc(encoder);
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + drm_panel_prepare(ldev->panel);
> + drm_panel_enable(ldev->panel);
> +}
> +
> +static void ltdc_rgb_encoder_disable(struct drm_encoder *encoder)
> +{
> + struct ltdc_device *ldev = encoder_to_ltdc(encoder);
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + drm_panel_disable(ldev->panel);
> + drm_panel_unprepare(ldev->panel);
> +}
> +
> +static const struct drm_encoder_helper_funcs ltdc_rgb_encoder_helper_funcs = {
> + .enable = ltdc_rgb_encoder_enable,
> + .disable = ltdc_rgb_encoder_disable,
> +};
> +
> +static const struct drm_encoder_funcs ltdc_rgb_encoder_funcs = {
> + .destroy = drm_encoder_cleanup,
> +};
> +
> +static struct drm_encoder *ltdc_rgb_encoder_create(struct drm_device *ddev)
> +{
> + struct drm_encoder *encoder;
> +
> + encoder = devm_kzalloc(ddev->dev, sizeof(*encoder), GFP_KERNEL);
> + if (!encoder)
> + return NULL;
> +
> + encoder->possible_crtcs = CRTC_MASK;
> + encoder->possible_clones = 0; /* No cloning support */
> +
> + drm_encoder_init(ddev, encoder, <dc_rgb_encoder_funcs,
> + DRM_MODE_ENCODER_DPI, NULL);
> +
> + drm_encoder_helper_add(encoder, <dc_rgb_encoder_helper_funcs);
> +
> + DRM_DEBUG_DRIVER("RGB encoder:%d created\n", encoder->base.id);
> +
> + return encoder;
> +}
> +
> +/*
> + * DRM_CONNECTOR
> + */
> +
> +static int ltdc_rgb_connector_get_modes(struct drm_connector *connector)
> +{
> + struct drm_device *ddev = connector->dev;
> + struct ltdc_device *ldev = ddev->dev_private;
> + int ret = 0;
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + if (ldev->panel)
> + ret = drm_panel_get_modes(ldev->panel);
> +
> + return ret < 0 ? 0 : ret;
> +}
> +
> +static struct drm_connector_helper_funcs ltdc_rgb_connector_helper_funcs = {
> + .get_modes = ltdc_rgb_connector_get_modes,
> +};
> +
> +static enum drm_connector_status
> +ltdc_rgb_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct ltdc_device *ldev = connector_to_ltdc(connector);
> +
> + return ldev->panel ? connector_status_connected :
> + connector_status_disconnected;
> +}
> +
> +static void ltdc_rgb_connector_destroy(struct drm_connector *connector)
> +{
> + DRM_DEBUG_DRIVER("\n");
> +
> + drm_connector_unregister(connector);
> + drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs ltdc_rgb_connector_funcs = {
> + .dpms = drm_atomic_helper_connector_dpms,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .detect = ltdc_rgb_connector_detect,
> + .destroy = ltdc_rgb_connector_destroy,
> + .reset = drm_atomic_helper_connector_reset,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +struct drm_connector *ltdc_rgb_connector_create(struct drm_device *ddev)
> +{
> + struct drm_connector *connector;
> + int err;
> +
> + connector = devm_kzalloc(ddev->dev, sizeof(*connector), GFP_KERNEL);
> + if (!connector) {
> + DRM_ERROR("Failed to allocate connector\n");
> + return NULL;
> + }
> +
> + connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> + err = drm_connector_init(ddev, connector, <dc_rgb_connector_funcs,
> + DRM_MODE_CONNECTOR_DPI);
> + if (err) {
> + DRM_ERROR("Failed to initialize connector\n");
> + return NULL;
> + }
> +
> + drm_connector_helper_add(connector, <dc_rgb_connector_helper_funcs);
> +
> + DRM_DEBUG_DRIVER("RGB connector:%d created\n", connector->base.id);
> +
> + return connector;
> +}
> +
> +static int ltdc_get_caps(struct drm_device *ddev)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> + u32 bus_width_log2, lcr, gc2r;
> +
> + /* at least 1 layer must be managed */
> + lcr = reg_read(ldev->regs, LTDC_LCR);
> +
> + ldev->caps.nb_layers = max_t(int, lcr, 1);
> +
> + /* set data bus width */
> + gc2r = reg_read(ldev->regs, LTDC_GC2R);
> + bus_width_log2 = (gc2r & GC2R_BW) >> 4;
> + ldev->caps.bus_width = 8 << bus_width_log2;
> + ldev->caps.hw_version = reg_read(ldev->regs, LTDC_IDR);
> +
> + switch (ldev->caps.hw_version) {
> + case HWVER_10200:
> + case HWVER_10300:
> + ldev->caps.reg_ofs = REG_OFS_NONE;
> + ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a0;
> + break;
> + case HWVER_20101:
> + ldev->caps.reg_ofs = REG_OFS_4;
> + ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a1;
> + break;
> + default:
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> +
> +static struct drm_panel *ltdc_get_panel(struct drm_device *ddev)
> +{
> + struct device *dev = ddev->dev;
> + struct device_node *np = dev->of_node;
> + struct device_node *entity, *port = NULL;
> + struct drm_panel *panel = NULL;
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + /*
> + * Parse ltdc node to get remote port and find RGB panel / HDMI slave
> + * If a dsi or a bridge (hdmi, lvds...) is connected to ltdc,
> + * a remote port & RGB panel will not be found.
> + */
> + for_each_endpoint_of_node(np, entity) {
> + if (!of_device_is_available(entity))
> + continue;
> +
> + port = of_graph_get_remote_port_parent(entity);
> + if (port) {
> + panel = of_drm_find_panel(port);
> + of_node_put(port);
> + if (panel) {
> + DRM_DEBUG_DRIVER("remote panel %s\n",
> + port->full_name);
> + } else {
> + DRM_DEBUG_DRIVER("panel missing\n");
> + of_node_put(entity);
> + }
> + }
> + }
> +
> + return panel;
> +}
> +
> +int ltdc_load(struct drm_device *ddev)
> +{
> + struct platform_device *pdev = to_platform_device(ddev->dev);
> + struct ltdc_device *ldev = ddev->dev_private;
> + struct device *dev = ddev->dev;
> + struct device_node *np = dev->of_node;
> + struct drm_encoder *encoder;
> + struct drm_connector *connector = NULL;
> + struct drm_crtc *crtc;
> + struct reset_control *rstc;
> + struct resource res;
> + int irq, ret, i;
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + ldev->panel = ltdc_get_panel(ddev);
> + if (!ldev->panel)
> + return -EPROBE_DEFER;
> +
> + rstc = of_reset_control_get(np, NULL);
> +
> + mutex_init(&ldev->err_lock);
> +
> + ldev->pixel_clk = devm_clk_get(dev, "lcd");
> + if (IS_ERR(ldev->pixel_clk)) {
> + DRM_ERROR("Unable to get lcd clock\n");
> + return -ENODEV;
> + }
> +
> + if (clk_prepare_enable(ldev->pixel_clk)) {
> + DRM_ERROR("Unable to prepare pixel clock\n");
> + return -ENODEV;
> + }
> +
> + if (of_address_to_resource(np, 0, &res)) {
> + DRM_ERROR("Unable to get resource\n");
> + return -ENODEV;
> + }
> +
> + ldev->regs = devm_ioremap_resource(dev, &res);
> + if (IS_ERR(ldev->regs)) {
> + DRM_ERROR("Unable to get ltdc registers\n");
> + return PTR_ERR(ldev->regs);
> + }
> +
> + for (i = 0; i < MAX_IRQ; i++) {
> + irq = platform_get_irq(pdev, i);
> + if (irq < 0)
> + continue;
> +
> + ret = devm_request_threaded_irq(dev, irq, ltdc_irq,
> + ltdc_irq_thread, IRQF_ONESHOT,
> + dev_name(dev), ddev);
> + if (ret) {
> + DRM_ERROR("Failed to register LTDC interrupt\n");
> + return ret;
> + }
> + }
> +
> + if (!IS_ERR(rstc))
> + reset_control_deassert(rstc);
> +
> + /* Disable interrupts */
> + reg_clear(ldev->regs, LTDC_IER,
> + IER_LIE | IER_RRIE | IER_FUIE | IER_TERRIE);
> +
> + ret = ltdc_get_caps(ddev);
> + if (ret) {
> + DRM_ERROR("hardware identifier (0x%08x) not supported!\n",
> + ldev->caps.hw_version);
> + return ret;
> + }
> +
> + DRM_INFO("ltdc hw version 0x%08x - ready\n", ldev->caps.hw_version);
> +
> + if (ldev->panel) {
> + encoder = ltdc_rgb_encoder_create(ddev);
> + if (!encoder) {
> + DRM_ERROR("Failed to create RGB encoder\n");
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + connector = ltdc_rgb_connector_create(ddev);
> + if (!connector) {
> + DRM_ERROR("Failed to create RGB connector\n");
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + ret = drm_mode_connector_attach_encoder(connector, encoder);
> + if (ret) {
> + DRM_ERROR("Failed to attach connector to encoder\n");
> + goto err;
> + }
> +
> + drm_panel_attach(ldev->panel, connector);
> + }
> +
> + crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
> + if (!crtc) {
> + DRM_ERROR("Failed to allocate crtc\n");
> + ret = -ENOMEM;
> + goto err;
> + }
> +
> + ret = ltdc_crtc_init(ddev, crtc);
> + if (ret) {
> + DRM_ERROR("Failed to init crtc\n");
> + goto err;
> + }
> +
> + ret = drm_vblank_init(ddev, NB_CRTC);
> + if (ret) {
> + DRM_ERROR("Failed calling drm_vblank_init()\n");
> + goto err;
> + }
> +
> + /* Allow usage of vblank without having to call drm_irq_install */
> + ddev->irq_enabled = 1;
> +
> + return 0;
> +err:
> + if (ldev->panel)
> + drm_panel_detach(ldev->panel);
> +
> + clk_disable_unprepare(ldev->pixel_clk);
> +
> + return ret;
> +}
> +
> +void ltdc_unload(struct drm_device *ddev)
> +{
> + struct ltdc_device *ldev = ddev->dev_private;
> +
> + DRM_DEBUG_DRIVER("\n");
> +
> + drm_vblank_cleanup(ddev);
> +
> + if (ldev->panel)
> + drm_panel_detach(ldev->panel);
> +
> + clk_disable_unprepare(ldev->pixel_clk);
> +}
> +
> +MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
> +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
> +MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
> +MODULE_AUTHOR("Mickael Reulier <mickael.reulier@st.com>");
> +MODULE_DESCRIPTION("STMicroelectronics ST DRM LTDC driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h
> new file mode 100644
> index 0000000..d7a9c73
> --- /dev/null
> +++ b/drivers/gpu/drm/stm/ltdc.h
> @@ -0,0 +1,40 @@
> +/*
> + * Copyright (C) STMicroelectronics SA 2017
> + *
> + * Authors: Philippe Cornu <philippe.cornu@st.com>
> + * Yannick Fertre <yannick.fertre@st.com>
> + * Fabien Dessenne <fabien.dessenne@st.com>
> + * Mickael Reulier <mickael.reulier@st.com>
> + *
> + * License terms: GNU General Public License (GPL), version 2
> + */
> +
> +#ifndef _LTDC_H_
> +#define _LTDC_H_
> +
> +struct ltdc_caps {
> + u32 hw_version; /* hardware version */
> + u32 nb_layers; /* number of supported layers */
> + u32 reg_ofs; /* register offset for applicable regs */
> + u32 bus_width; /* bus width (32 or 64 bits) */
> + const u32 *pix_fmt_hw; /* supported pixel formats */
> +};
> +
> +struct ltdc_device {
> + struct drm_fbdev_cma *fbdev;
> + void __iomem *regs;
> + struct clk *pixel_clk; /* lcd pixel clock */
> + struct drm_panel *panel;
> + struct mutex err_lock; /* protecting error_status */
> + struct ltdc_caps caps;
> + u32 clut[256]; /* color look up table */
> + u32 error_status;
> + u32 irq_status;
> +};
> +
> +int ltdc_crtc_enable_vblank(struct drm_device *dev, unsigned int pipe);
> +void ltdc_crtc_disable_vblank(struct drm_device *dev, unsigned int pipe);
> +int ltdc_load(struct drm_device *ddev);
> +void ltdc_unload(struct drm_device *ddev);
> +
> +#endif
>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
^ permalink raw reply
* [PATCH v7 5/9] MAINTAINERS: add maintainers for DRM STM driver
From: Neil Armstrong @ 2017-04-14 12:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1492164819-10513-6-git-send-email-yannick.fertre@st.com>
On 04/14/2017 12:13 PM, Yannick Fertre wrote:
> Add Philippe Cornu and myself as maintainers.
>
> Signed-off-by: Yannick Fertre <yannick.fertre@st.com>
> ---
> MAINTAINERS | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c36dfae..84cf73f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4424,6 +4424,15 @@ S: Maintained
> F: drivers/gpu/drm/sti
> F: Documentation/devicetree/bindings/display/st,stih4xx.txt
>
> +DRM DRIVERS FOR STM
> +M: Yannick Fertre <yannick.fertre@st.com>
> +M: Philippe Cornu <philippe.cornu@st.com>
> +L: dri-devel at lists.freedesktop.org
> +T: git git://anongit.freedesktop.org/drm/drm-misc
> +S: Maintained
> +F: drivers/gpu/drm/stm
> +F: Documentation/devicetree/bindings/display/st,stm32-ltdc.txt
> +
> DRM DRIVER FOR TDFX VIDEO CARDS
> S: Orphan / Obsolete
> F: drivers/gpu/drm/tdfx/
>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
^ permalink raw reply
* [PATCH 1/2] arm64: allwinner: a64: add EHCI0/OHCI0 nodes to A64 DTSI
From: Icenowy Zheng @ 2017-04-14 13:15 UTC (permalink / raw)
To: linux-arm-kernel
Allwinner A64 SoC features a pair of EHCI/OHCI controllers that can be
set to wire to USB0 port (the OTG-capable one), which can be used to
provide a better performance in host mode.
Add their device tree nodes.
Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
index c7f669f5884f..65a344d9cea4 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
@@ -204,6 +204,28 @@
#phy-cells = <1>;
};
+ ehci0: usb at 01c1a000 {
+ compatible = "allwinner,sun50i-a64-ehci", "generic-ehci";
+ reg = <0x01c1a000 0x100>;
+ interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_OHCI0>,
+ <&ccu CLK_BUS_EHCI0>,
+ <&ccu CLK_USB_OHCI0>;
+ resets = <&ccu RST_BUS_OHCI0>,
+ <&ccu RST_BUS_EHCI0>;
+ status = "disabled";
+ };
+
+ ohci0: usb at 01c1a400 {
+ compatible = "allwinner,sun50i-a64-ohci", "generic-ohci";
+ reg = <0x01c1a400 0x100>;
+ interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_OHCI0>,
+ <&ccu CLK_USB_OHCI0>;
+ resets = <&ccu RST_BUS_OHCI0>;
+ status = "disabled";
+ };
+
ehci1: usb at 01c1b000 {
compatible = "allwinner,sun50i-a64-ehci", "generic-ehci";
reg = <0x01c1b000 0x100>;
--
2.12.2
^ permalink raw reply related
* [PATCH 2/2] arm64: allwinner: a64: enable EHCI0/OHCI0 for Pine64
From: Icenowy Zheng @ 2017-04-14 13:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170414131555.9431-1-icenowy@aosc.io>
As we have USB0 controller switch available on A64, we should now enable
the EHCI0/OHCI0 controllers for Pine64.
Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
index c680ed385da3..4782add50b94 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
@@ -66,6 +66,10 @@
};
};
+&ehci0 {
+ status = "okay";
+};
+
&ehci1 {
status = "okay";
};
@@ -91,6 +95,10 @@
status = "okay";
};
+&ohci0 {
+ status = "okay";
+};
+
&ohci1 {
status = "okay";
};
--
2.12.2
^ permalink raw reply related
* [PATCH] ARM32: Support mremap() for sigpage/vDSO
From: kbuild test robot @ 2017-04-14 13:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170414101033.4780-1-dsafonov@virtuozzo.com>
Hi Dmitry,
[auto build test WARNING on linus/master]
[also build test WARNING on v4.11-rc6 next-20170413]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Dmitry-Safonov/ARM32-Support-mremap-for-sigpage-vDSO/20170414-190018
config: arm-sunxi_defconfig (attached as .config)
compiler: arm-linux-gnueabi-gcc (Debian 6.1.1-9) 6.1.1 20160705
reproduce:
wget https://raw.githubusercontent.com/01org/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=arm
All warnings (new ones prefixed by >>):
arch/arm/kernel/vdso.c: In function 'vdso_mremap':
>> arch/arm/kernel/vdso.c:69:28: warning: assignment makes integer from pointer without a cast [-Wint-conversion]
current->mm->context.vdso = (void __user *)new_vma->vm_start;
^
vim +69 arch/arm/kernel/vdso.c
53 .name = "[vvar]",
54 .pages = &vdso_data_page,
55 };
56
57 static int vdso_mremap(const struct vm_special_mapping *sm,
58 struct vm_area_struct *new_vma)
59 {
60 unsigned long new_size = new_vma->vm_end - new_vma->vm_start;
61 unsigned long vdso_size;
62
63 /* without VVAR page */
64 vdso_size = (vdso_total_pages - 1) << PAGE_SHIFT;
65
66 if (vdso_size != new_size)
67 return -EINVAL;
68
> 69 current->mm->context.vdso = (void __user *)new_vma->vm_start;
70
71 return 0;
72 }
73
74 static struct vm_special_mapping vdso_text_mapping __ro_after_init = {
75 .name = "[vdso]",
76 .mremap = vdso_mremap,
77 };
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
-------------- next part --------------
A non-text attachment was scrubbed...
Name: .config.gz
Type: application/gzip
Size: 20767 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170414/e61d08a5/attachment-0001.gz>
^ permalink raw reply
* [PATCHv2] ARM32: Support mremap() for sigpage/vDSO
From: Dmitry Safonov @ 2017-04-14 13:25 UTC (permalink / raw)
To: linux-arm-kernel
CRIU restores application mappings on the same place where they
were before Checkpoint. That means, that we need to move vDSO
and sigpage during restore on exactly the same place where
they were before C/R.
Make mremap() code update mm->context.{sigpage,vdso} pointers
during VMA move. Sigpage is used for landing after handling
a signal - if the pointer is not updated during moving, the
application might crash on any signal after mremap().
vDSO pointer on ARM32 is used only for setting auxv at this moment,
update it during mremap() in case of future usage.
Without those updates, current work of CRIU on ARM32 is not reliable.
Historically, we error Checkpointing if we find vDSO page on ARM32
and suggest user to disable CONFIG_VDSO.
But that's not correct - it goes from x86 where signal processing
is ended in vDSO blob. For arm32 it's sigpage, which is not disabled
with `CONFIG_VDSO=n'.
Looks like C/R was working by luck - because userspace on ARM32 at
this moment always sets SA_RESTORER.
Cc: linux-arm-kernel at lists.infradead.org
Cc: Russell King <linux@armlinux.org.uk>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Pavel Emelyanov <xemul@virtuozzo.com>
Cc: Christopher Covington <cov@codeaurora.org>
Signed-off-by: Dmitry Safonov <dsafonov@virtuozzo.com>
---
v2: (buildbot) Fix (unsinged long) to (void*) cast warning.
arch/arm/kernel/process.c | 8 ++++++++
arch/arm/kernel/vdso.c | 18 ++++++++++++++++++
arch/x86/entry/vdso/vma.c | 3 ---
mm/mmap.c | 4 ++++
4 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c
index 939e8b58c59d..1e6039cac68d 100644
--- a/arch/arm/kernel/process.c
+++ b/arch/arm/kernel/process.c
@@ -404,9 +404,17 @@ static unsigned long sigpage_addr(const struct mm_struct *mm,
static struct page *signal_page;
extern struct page *get_signal_page(void);
+static int sigpage_mremap(const struct vm_special_mapping *sm,
+ struct vm_area_struct *new_vma)
+{
+ current->mm->context.sigpage = new_vma->vm_start;
+ return 0;
+}
+
static const struct vm_special_mapping sigpage_mapping = {
.name = "[sigpage]",
.pages = &signal_page,
+ .mremap = sigpage_mremap,
};
int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
diff --git a/arch/arm/kernel/vdso.c b/arch/arm/kernel/vdso.c
index 53cf86cf2d1a..a4d6dc0f2427 100644
--- a/arch/arm/kernel/vdso.c
+++ b/arch/arm/kernel/vdso.c
@@ -54,8 +54,26 @@ static const struct vm_special_mapping vdso_data_mapping = {
.pages = &vdso_data_page,
};
+static int vdso_mremap(const struct vm_special_mapping *sm,
+ struct vm_area_struct *new_vma)
+{
+ unsigned long new_size = new_vma->vm_end - new_vma->vm_start;
+ unsigned long vdso_size;
+
+ /* without VVAR page */
+ vdso_size = (vdso_total_pages - 1) << PAGE_SHIFT;
+
+ if (vdso_size != new_size)
+ return -EINVAL;
+
+ current->mm->context.vdso = new_vma->vm_start;
+
+ return 0;
+}
+
static struct vm_special_mapping vdso_text_mapping __ro_after_init = {
.name = "[vdso]",
+ .mremap = vdso_mremap,
};
struct elfinfo {
diff --git a/arch/x86/entry/vdso/vma.c b/arch/x86/entry/vdso/vma.c
index 226ca70dc6bd..363730caa60e 100644
--- a/arch/x86/entry/vdso/vma.c
+++ b/arch/x86/entry/vdso/vma.c
@@ -77,9 +77,6 @@ static int vdso_mremap(const struct vm_special_mapping *sm,
if (image->size != new_size)
return -EINVAL;
- if (WARN_ON_ONCE(current->mm != new_vma->vm_mm))
- return -EFAULT;
-
vdso_fix_landing(image, new_vma);
current->mm->context.vdso = (void __user *)new_vma->vm_start;
diff --git a/mm/mmap.c b/mm/mmap.c
index bfbe8856d134..534aef99cfe9 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -3152,8 +3152,12 @@ static int special_mapping_mremap(struct vm_area_struct *new_vma)
{
struct vm_special_mapping *sm = new_vma->vm_private_data;
+ if (WARN_ON_ONCE(current->mm != new_vma->vm_mm))
+ return -EFAULT;
+
if (sm->mremap)
return sm->mremap(sm, new_vma);
+
return 0;
}
--
2.12.2
^ permalink raw reply related
* [PATCH 4/5] Hot-remove implementation for arm64
From: Andrea Reale @ 2017-04-14 14:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170411171210.GD32267@leverpostej>
Hi Mark,
thanks for your thorough feedback, it is really appreciated.
I have a few comments and I'd like to ask for further clarification,
if you don't mind, in order to help us work on a new version of the
patch.
Before I go into details, let me point out that, as you have noted
yourself, the structure of the patch is largely derived from the
x86_64 hot-remove code (mostly introduced in commit ae9aae9eda2db7
"memory-hotplug: common APIs to support page tables hot-remove") trying
to account for architectural differences. In this process, I guess it
is likely that I might have made assumptions that are true for x86_64
but do not hold for arm64. Whenever you feel this is the case, I would
be really grateful if you could help identify them.
Please, find detailed replies to your comments in-line.
On Tue, Apr 11, 2017 at 06:12:11PM +0100, Mark Rutland wrote:
> Hi,
>
> On Tue, Apr 11, 2017 at 03:55:42PM +0100, Andrea Reale wrote:
> > +static inline unsigned long pmd_page_vaddr(pmd_t pmd)
> > +{
> > + return (unsigned long) __va(pmd_page_paddr(pmd));
> > +}
> > +
> > /* Find an entry in the third-level page table. */
> > #define pte_index(addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
> >
> > @@ -450,6 +455,11 @@ static inline phys_addr_t pud_page_paddr(pud_t pud)
> > return pud_val(pud) & PHYS_MASK & (s32)PAGE_MASK;
> > }
> >
> > +static inline unsigned long pud_page_vaddr(pud_t pud)
> > +{
> > + return (unsigned long) __va(pud_page_paddr(pud));
> > +}
> > +
> > /* Find an entry in the second-level page table. */
> > #define pmd_index(addr) (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
> >
> > @@ -502,6 +512,11 @@ static inline phys_addr_t pgd_page_paddr(pgd_t pgd)
> > return pgd_val(pgd) & PHYS_MASK & (s32)PAGE_MASK;
> > }
> >
> > +static inline unsigned long pgd_page_vaddr(pgd_t pgd)
> > +{
> > + return (unsigned long) __va(pgd_page_paddr(pgd));
> > +}
> > +
>
> These duplicate the existing p*d_offset*() functions, and I do not think
> they are necessary. More on that below.
I agree they are redundant. It just seemed cleaner rather than
offsetting 0, but fair enough.
> [...]
>
> > @@ -551,7 +551,6 @@ int arch_add_memory(int nid, u64 start, u64 size, bool for_device)
> > unsigned long nr_pages = size >> PAGE_SHIFT;
> > unsigned long end_pfn = start_pfn + nr_pages;
> > unsigned long max_sparsemem_pfn = 1UL << (MAX_PHYSMEM_BITS-PAGE_SHIFT);
> > - unsigned long pfn;
> > int ret;
> >
> > if (end_pfn > max_sparsemem_pfn) {
>
> Should this have been part of a prior patch?
>
> This patch doesn't remove any users of this variable.
>
> [...]
Indeed, the unused pfn variable is a leftover of patch 3/5. We will
fix that in the next version.
> > +static void free_pagetable(struct page *page, int order, bool direct)
>
> This "direct" parameter needs a better name, and a description in a
> comment block above this function.
The name direct is inherited directly from the x86_64 hot remove code.
It serves to distinguish if we are removing either a pagetable page that
is mapping to the direct mapping space (I think it is called also linear
mapping area somewhere else) or a pagetable page or a data page
from vmemmap.
In this specific set of functions, the flag is needed because the various
alloc_init_p*d used for allocating entries for direct memory mapping
rely on pgd_pgtable_alloc, which in its turn calls pgtable_page_ctor;
hence, we need to call the dtor. On the contrary, vmemmap entries
are created using vmemmap_alloc_block (from within vmemmap_populate),
which does not call pgtable_page_ctor when allocating pages.
I am not sure I understand why the pgtable_page_ctor is not called when
allocating vmemmap page tables, but that's the current situation.
> > +{
> > + unsigned long magic;
> > + unsigned int nr_pages = 1 << order;
> > + struct vmem_altmap *altmap = to_vmem_altmap((unsigned long) page);
> > +
> > + if (altmap) {
> > + vmem_altmap_free(altmap, nr_pages);
> > + return;
> > + }
> > +
> > + /* bootmem page has reserved flag */
> > + if (PageReserved(page)) {
> > + __ClearPageReserved(page);
> > +
> > + magic = (unsigned long)page->lru.next;
> > + if (magic == SECTION_INFO || magic == MIX_SECTION_INFO) {
> > + while (nr_pages--)
> > + put_page_bootmem(page++);
> > + } else {
> > + while (nr_pages--)
> > + free_reserved_page(page++);
> > + }
> > + } else {
> > + /*
> > + * Only direct pagetable allocation (those allocated via
> > + * hotplug) call the pgtable_page_ctor; vmemmap pgtable
> > + * allocations don't.
> > + */
> > + if (direct)
> > + pgtable_page_dtor(page);
> > +
> > + free_pages((unsigned long)page_address(page), order);
> > + }
> > +}
>
> This largely looks like a copy of the x86 code. Why can that not be
> factored out to an arch-neutral location?
Yes it probably can - the only difference being calling
pgtable_page_dtor when it needs to - but I am not confident enough to
say that it would really be architecture neutral or just specific to
only arm64 and x86. For example, I don't see this used anywhere else
for hot-removing memory.
(Actually, also a large part of remove_*_table and free_*_table could
probably be factored, but I wouldn't be sure how to deal with the
differences in the pgtable.h macros used throughout)
> > +
> > +static void free_pte_table(pmd_t *pmd, bool direct)
> > +{
> > + pte_t *pte_start, *pte;
> > + struct page *page;
> > + int i;
> > +
> > + pte_start = (pte_t *) pmd_page_vaddr(*pmd);
> > + /* Check if there is no valid entry in the PMD */
> > + for (i = 0; i < PTRS_PER_PTE; i++) {
> > + pte = pte_start + i;
> > + if (!pte_none(*pte))
> > + return;
> > + }
>
> If we must walk the tables in this way, please use the existing pattern
> from arch/arm64/mm/dump.c.
>
> e.g.
>
> pte_t *pte;
>
> pte = pte_offset_kernel(pmd, 0UL);
> for (i = 0; i < PTRS_PER_PTE; i++, pte++)
> if (!pte_none)
> return;
Thanks.
> > +
> > + page = pmd_page(*pmd);
> > +
> > + free_pagetable(page, 0, direct);
>
> The page has been freed here, and may be subject to arbitrary
> modification...
>
> > +
> > + /*
> > + * This spin lock could be only taken in _pte_aloc_kernel
> > + * in mm/memory.c and nowhere else (for arm64). Not sure if
> > + * the function above can be called concurrently. In doubt,
> > + * I am living it here for now, but it probably can be removed
> > + */
> > + spin_lock(&init_mm.page_table_lock);
> > + pmd_clear(pmd);
>
> ... but we only remove it from the page tables here, so the page table
> walkers can see junk in the tables, were the page reused in the mean
> timer.
>
> After clearing the PMD, it needs to be cleared from TLBs. We allow
> partial walks to be cached, so the TLBs may still start walking this
> entry or beyond.
I guess that the safe approach would be something along the lines:
1. clear the page table
2. flush the tlbs
3. free the page
am I mistaken? When I am flushing intermediate p*d entries, would it be
more appropriate to use something like __flush_tlb_pgtable() to clear
cached partial walks rather than flushing the whole table? I mean,
hot-remove is not going to be a frequent operation anyway, so I don't
think that flushing the whole tlb would be a great deal of harm
anyway.
My question at this point would be: how come that the code structure above
works for x86_64 hot-remove? My assumption, when I was writing this, was
that there would be no problem since this code is called when we are sure
that all the memory mapped by these entries has been already offlined,
so nobody should be using those VAs anyway (also considering that there
cannot be any two mem hot-plug/remove actions running concurrently).
Is that correct?
> > + spin_unlock(&init_mm.page_table_lock);
> > +}
>
> The same comments apply to all the free_p*d_table() functions, so I
> shan't repeat them.
>
> [...]
>
> > +static void remove_pte_table(pte_t *pte, unsigned long addr,
> > + unsigned long end, bool direct)
> > +{
> > + unsigned long next;
> > + void *page_addr;
> > +
> > + for (; addr < end; addr = next, pte++) {
> > + next = (addr + PAGE_SIZE) & PAGE_MASK;
> > + if (next > end)
> > + next = end;
>
> Please use the usual p*d_addr_end() functions. See alloc_init_pmd() and
> friends in arch/arm64/mm/mmu.c for examples of how to use them.
we used the p*d_addr_end family of functions when dealing with p*d(s). I
cannot identify an equivalent for pte entries. Would you recommend adding
a pte_addr_end macro in pgtable.h?
> > +
> > + if (!pte_present(*pte))
> > + continue;
> > +
> > + if (PAGE_ALIGNED(addr) && PAGE_ALIGNED(next)) {
>
> When would those addresses *not* be page-aligned? By construction, next
> must be. Unplugging partial pages of memory makes no sense, so surely
> addr is page-aligned when passed in?
The issue here is that this function is called in one of two cases:
1. to clear pagetables of directly mapped (linear) memory
2. Pagetables (and corresponding data pages) for vmemmap.
It is my understanding that, in the second case, we might be clearing
only part of the page content (i.e, only a few struct pages). Note that
next is page aligned by construction only if next <= end.
> > + /*
> > + * Do not free direct mapping pages since they were
> > + * freed when offlining, or simplely not in use.
> > + */
> > + if (!direct)
> > + free_pagetable(pte_page(*pte), 0, direct);
> > +
> > + /*
> > + * This spin lock could be only
> > + * taken in _pte_aloc_kernel in
> > + * mm/memory.c and nowhere else
> > + * (for arm64). Not sure if the
> > + * function above can be called
> > + * concurrently. In doubt,
> > + * I am living it here for now,
> > + * but it probably can be removed.
> > + */
> > + spin_lock(&init_mm.page_table_lock);
> > + pte_clear(&init_mm, addr, pte);
> > + spin_unlock(&init_mm.page_table_lock);
> > + } else {
> > + /*
> > + * If we are here, we are freeing vmemmap pages since
> > + * direct mapped memory ranges to be freed are aligned.
> > + *
> > + * If we are not removing the whole page, it means
> > + * other page structs in this page are being used and
> > + * we canot remove them. So fill the unused page_structs
> > + * with 0xFD, and remove the page when it is wholly
> > + * filled with 0xFD.
> > + */
> > + memset((void *)addr, PAGE_INUSE, next - addr);
>
> What's special about 0xFD?
Just used it as a constant symmetrically to x86_64 code.
> Why do we need to mess with the page array in this manner? Why can't we
> detect when a range is free by querying memblock, for example?
I am not sure I get your suggestion. I guess that the logic here
is that I cannot be sure that I can free the full page because other
entries might be in use for active vmemmap mappings. So we just "mark"
the unused once and only free the page when all of it is marked. See
again commit ae9aae9eda2db71, where all this comes from.
> > +
> > + page_addr = page_address(pte_page(*pte));
> > + if (!memchr_inv(page_addr, PAGE_INUSE, PAGE_SIZE)) {
> > + free_pagetable(pte_page(*pte), 0, direct);
> > +
> > + /*
> > + * This spin lock could be only
> > + * taken in _pte_aloc_kernel in
> > + * mm/memory.c and nowhere else
> > + * (for arm64). Not sure if the
> > + * function above can be called
> > + * concurrently. In doubt,
> > + * I am living it here for now,
> > + * but it probably can be removed.
> > + */
> > + spin_lock(&init_mm.page_table_lock);
> > + pte_clear(&init_mm, addr, pte);
> > + spin_unlock(&init_mm.page_table_lock);
>
> This logic appears to be duplicated with the free_*_table functions, and
> looks incredibly suspicious.
>
> To me, it doesn't make sense that we'd need the lock *only* to alter the
> leaf entries.
I admit I am still confused by the use of the page_table_lock and when
and where it should be used. Any hint on that would be more than
welcome.
> > + }
> > + }
> > + }
> > +
> > + // I am adding this flush here in simmetry to the x86 code.
> > + // Why do I need to call it here and not in remove_p[mu]d
> > + flush_tlb_all();
>
> If the page tables weren't freed until this point, it might be that this
> is just amortizing the cost of removing them from the TLBs.
>
> Given that they're freed first, this makes no sense to me.
Please, see above.
> > +}
>
> The same commenst apply to all the remove_p*d_table() functions, so I
> shan't repeat them.
>
> > +
> > +static void remove_pmd_table(pmd_t *pmd, unsigned long addr,
> > + unsigned long end, bool direct)
> > +{
> > + unsigned long next;
> > + void *page_addr;
> > + pte_t *pte;
> > +
> > + for (; addr < end; addr = next, pmd++) {
> > + next = pmd_addr_end(addr, end);
> > +
> > + if (!pmd_present(*pmd))
> > + continue;
> > +
> > + // check if we are using 2MB section mappings
> > + if (pmd_sect(*pmd)) {
> > + if (PAGE_ALIGNED(addr) && PAGE_ALIGNED(next)) {
>
> Surely you're intending to check if you can free the whole pmd? i.e.
> that addr and next are pmd-aligned?
Indeed, that's a mistake. It should have been IS_ALIGNED(addr, PMD_SIZE).
> Can we ever be in a situation where we're requested to free a partial
> pmd that could be section mapped?
Yes, as I said above, for vmemmap mappings.
> If that's the case, we'll *need* to split the pmd, which we can't do on
> live page tables.
Please, see below.
> > + if (!direct) {
> > + free_pagetable(pmd_page(*pmd),
> > + get_order(PMD_SIZE), direct);
> > + }
> > + /*
> > + * This spin lock could be only
> > + * taken in _pte_aloc_kernel in
> > + * mm/memory.c and nowhere else
> > + * (for arm64). Not sure if the
> > + * function above can be called
> > + * concurrently. In doubt,
> > + * I am living it here for now,
> > + * but it probably can be removed.
> > + */
> > + spin_lock(&init_mm.page_table_lock);
> > + pmd_clear(pmd);
> > + spin_unlock(&init_mm.page_table_lock);
> > + } else {
> > + /* If here, we are freeing vmemmap pages. */
> > + memset((void *)addr, PAGE_INUSE, next - addr);
> > +
> > + page_addr = page_address(pmd_page(*pmd));
> > + if (!memchr_inv(page_addr, PAGE_INUSE,
> > + PMD_SIZE)) {
> > + free_pagetable(pmd_page(*pmd),
> > + get_order(PMD_SIZE), direct);
> > +
> > + /*
> > + * This spin lock could be only
> > + * taken in _pte_aloc_kernel in
> > + * mm/memory.c and nowhere else
> > + * (for arm64). Not sure if the
> > + * function above can be called
> > + * concurrently. In doubt,
> > + * I am living it here for now,
> > + * but it probably can be removed.
> > + */
> > + spin_lock(&init_mm.page_table_lock);
> > + pmd_clear(pmd);
> > + spin_unlock(&init_mm.page_table_lock);
> > + }
>
> I don't think this is correct.
>
> If we're getting rid of a partial pmd, we *must* split the pmd.
> Otherwise, we're leaving bits mapped that should not be. If we split the
> pmd, we can free the individual pages as we would for a non-section
> mapping.
>
> As above, we can't split block entries within live tables, so that will
> be painful at best.
>
> If we can't split a pmd, hen we cannot free a partial pmd, and will need
> to reject request to do so.
>
> The same comments (with s/pmu/pud/, etc) apply for the higher level
> remove_p*d_table functions.
>
> [...]
This only happens when we are clearing vmemmap memory. My understanding
is that the whole hack of marking the content of partially unused areas
with the 0xFD constant is exactly to avoid splitting the PMD, but instead
to only clear the full area when we realize that there's no valid struct
page in it anymore. When would this kind of use be source of problems?
I am also realizing that vememmaps never use section maps at pud level,
so this code would only need to stay when clearing pmds and ptes.
> > +void remove_pagetable(unsigned long start, unsigned long end, bool direct)
> > +{
> > + unsigned long next;
> > + unsigned long addr;
> > + pgd_t *pgd;
> > + pud_t *pud;
> > +
> > + for (addr = start; addr < end; addr = next) {
> > + next = pgd_addr_end(addr, end);
> > +
> > + pgd = pgd_offset_k(addr);
> > + if (pgd_none(*pgd))
> > + continue;
> > +
> > + pud = pud_offset(pgd, addr);
> > + remove_pud_table(pud, addr, next, direct);
> > + /*
> > + * When the PUD is folded on the PGD (three levels of paging),
> > + * I did already clear the PMD page in free_pmd_table,
> > + * and reset the corresponding PGD==PUD entry.
> > + */
> > +#if CONFIG_PGTABLE_LEVELS > 3
> > + free_pud_table(pgd, direct);
> > #endif
>
> This looks suspicious. Shouldn't we have a similar check for PMD, to
> cater for CONFIG_PGTABLE_LEVELS == 2? e.g. 64K pages, 42-bit VA.
>
> We should be able to hide this distinction in helpers for both cases.
True, we will fix it in the next version.
> > + }
> > +
> > + flush_tlb_all();
>
> This is too late to be useful.
>
> Thanks,
> Mark.
Thanks again for your comments.
Best,
Andrea
^ permalink raw reply
* [PATCH v3 03/21] ARM: dts: omap: Add generic compatible string for I2C EEPROM
From: Tony Lindgren @ 2017-04-14 14:17 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170414010445.21727-4-javier@osg.samsung.com>
* Javier Martinez Canillas <javier@osg.samsung.com> [170413 18:08]:
> The at24 driver allows to register I2C EEPROM chips using different vendor
> and devices, but the I2C subsystem does not take the vendor into account
> when matching using the I2C table since it only has device entries.
>
> But when matching using an OF table, both the vendor and device has to be
> taken into account so the driver defines only a set of compatible strings
> using the "atmel" vendor as a generic fallback for compatible I2C devices.
>
> So add this generic fallback to the device node compatible string to make
> the device to match the driver using the OF device ID table.
>
> Signed-off-by: Javier Martinez Canillas <javier@osg.samsung.com>
Looks good to me assuming this will get merged in a single series:
Acked-by: Tony Lindgren <tony@atomide.com>
Or if you want me to pick this patch separately, let me know.
Regards,
Tony
^ permalink raw reply
* [PATCH V2] PM / OPP: Use - instead of @ for DT entries
From: Tony Lindgren @ 2017-04-14 14:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <70e7c7ee13722ab9c73cb073f88502eaf1ada5f5.1491816050.git.viresh.kumar@linaro.org>
* Viresh Kumar <viresh.kumar@linaro.org> [170410 02:24]:
> Compiling the DT file with W=1, DTC warns like follows:
>
> Warning (unit_address_vs_reg): Node /opp_table0/opp at 1000000000 has a
> unit name, but no reg property
>
> Fix this by replacing '@' with '-' as the OPP nodes will never have a
> "reg" property.
>
> Reported-by: Krzysztof Kozlowski <krzk@kernel.org>
> Reported-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> Suggested-by: Mark Rutland <mark.rutland@arm.com>
> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com> (sunxi)
> Reviewed-by: Chanwoo Choi <cw00.choi@samsung.com>
> Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>
Looks good to me too:
Acked-by: Tony Lindgren <tony@atomide.com>
^ permalink raw reply
* [GIT PULL] Immutable branch between MFD and IIO due for the v4.12 merge window
From: Jonathan Cameron @ 2017-04-14 14:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170411100528.2zif3fyi7uoyw33n@dell>
On 11/04/17 11:05, Lee Jones wrote:
> Enjoy!
>
> The following changes since commit c1ae3cfa0e89fa1a7ecc4c99031f5e9ae99d9201:
>
> Linux 4.11-rc1 (2017-03-05 12:59:56 -0800)
>
> are available in the git repository at:
>
> git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git ib-mfd-iio-v4.12
>
> for you to fetch changes up to f2499ab450d3052097ba53a7d763f767935c0c59:
>
> iio: adc: add support for X-Powers AXP20X and AXP22X PMICs ADCs (2017-04-11 11:02:33 +0100)
>
> ----------------------------------------------------------------
> Immutable branch between MFD and IIO due for the v4.12 merge window
>
> ----------------------------------------------------------------
> Quentin Schulz (1):
> iio: adc: add support for X-Powers AXP20X and AXP22X PMICs ADCs
>
> drivers/iio/adc/Kconfig | 10 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/axp20x_adc.c | 617 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 628 insertions(+)
> create mode 100644 drivers/iio/adc/axp20x_adc.c
>
Hi Lee,
Thanks for doing this, but the reason it was going to go through your
tree in the first place was a dependency on
commit 4707274714ef ("mfd: axp20x: Correct name of temperature data ADC registers")
Not present in the immutable branch.
There isn't much time for anything else going on around this driver though
so other than a possible merge conflict on the Kconfig and Makefile shouldn't
matter if this just goes through mfd. (famous last words ;)
Jonathan
^ permalink raw reply
* [PATCH 3/8] ARM: dts: imx7s: Adjust anatop-enable-bit for 'reg_1p0d'
From: Andrey Smirnov @ 2017-04-14 14:33 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170414032853.GF14915@dragon>
On Thu, Apr 13, 2017 at 8:28 PM, Shawn Guo <shawnguo@kernel.org> wrote:
> On Thu, Apr 13, 2017 at 06:32:37AM -0700, Andrey Smirnov wrote:
>> In PMU_REG_1P0Dn ENABLE_LINREG is bit 0. Bit 31 is called OVERRIDE and
>> it serves the function of granting permission to GPC IP block to alter
>> various bit-fields of the register. The reason why this property, that
>> trickeld here from Freescale BSP, is set to 31 is because in the code
>> it came from it is used in conjunction with a notifier handler for
>> REGULATOR_EVENT_PRE_DO_ENABLE and REGULATOR_EVENT_PRE_DO_DISABLE
>> events (not found in upstream kernel) that triggers GPC to start
>> manipulating aforementioned other bitfields.
>>
>> Since:
>> a) none of the aforementioned machinery is implemented by
>> upstream
>> b) using 'anatop-enable-bit' in that capacity is a bit of a
>> semantic stretch
>>
>> simplify the situation by setting the value of 'anatop-enable-bit' to
>> point to ENABLE_LINREG (same as i.MX6).
>>
>> Cc: yurovsky at gmail.com
>> Cc: Sascha Hauer <kernel@pengutronix.de>
>> Cc: Fabio Estevam <fabio.estevam@nxp.com>
>> Cc: Rob Herring <robh+dt@kernel.org>
>> Cc: Mark Rutland <mark.rutland@arm.com>
>> Cc: Russell King <linux@armlinux.org.uk>
>> Cc: devicetree at vger.kernel.org
>> Cc: linux-kernel at vger.kernel.org
>> Cc: linux-arm-kernel at lists.infradead.org
>> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
>
> Since patch 1 ~ 3 are all about adding anatop-enable-bit, can we squash
> them into one patch?
OK. Will do in v2.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox