* Re: [PATCH 1/2] powerpc/8610: add probing for individual DMA channels, not just DMA controllers
From: Timur Tabi @ 2010-05-04 15:11 UTC (permalink / raw)
To: Mark Brown; +Cc: linuxppc-dev, kumar.gala, alsa-devel, lrg
In-Reply-To: <20100504145430.GC3651@opensource.wolfsonmicro.com>
Mark Brown wrote:
> On Mon, May 03, 2010 at 04:54:15PM -0500, Timur Tabi wrote:
>
>> { .compatible = "simple-bus", },
>> - { .compatible = "gianfar", },
>> + /* So that the DMA channel nodes can be probed individually: */
>> + { .compatible = "fsl,eloplus-dma", },
>> {}
>
> The removal of gianfar looks a bit surprising here - if it's not a
> mistake a note in the changelog would be useful.
The changelog says this:
Also remove the "gianfar" compatible from mpc8610_ids[], since there is no
gianfar (or any other networking device) on the 8610.
The 8610 is almost unique among our high-end PowerPC chips in that it
literally has no on-board networking. The board provides networking via a
PCI device.
--
Timur Tabi
Linux kernel developer at Freescale
^ permalink raw reply
* Re: [RFC][PATCH resend 8/12] asm-generic: bitops: introduce le bit offset macro
From: Arnd Bergmann @ 2010-05-04 15:03 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504220500.7695cd66.takuya.yoshikawa@gmail.com>
On Tuesday 04 May 2010, Takuya Yoshikawa wrote:
>
> Although we can use *_le_bit() helpers to treat bitmaps le arranged,
> having le bit offset calculation as a seperate macro gives us more freedom.
>
> For example, KVM has le arranged dirty bitmaps for VGA, live-migration
> and they are used in user space too. To avoid bitmap copies between kernel
> and user space, we want to update the bitmaps in user space directly.
> To achive this, le bit offset with *_user() functions help us a lot.
>
> So let us use the le bit offset calculation part by defining it as a new
> macro: generic_le_bit_offset() .
Does this work correctly if your user space is 32 bits (i.e. unsigned long
is different size in user space and kernel) in both big- and little-endian
systems?
I'm not sure about all the details, but I think you cannot in general share
bitmaps between user space and kernel because of this.
Arnd
^ permalink raw reply
* Re: [PATCH 1/2] powerpc/8610: add probing for individual DMA channels, not just DMA controllers
From: Mark Brown @ 2010-05-04 14:54 UTC (permalink / raw)
To: Timur Tabi; +Cc: linuxppc-dev, kumar.gala, alsa-devel, lrg
In-Reply-To: <1272923655-31807-1-git-send-email-timur@freescale.com>
On Mon, May 03, 2010 at 04:54:15PM -0500, Timur Tabi wrote:
> { .compatible = "simple-bus", },
> - { .compatible = "gianfar", },
> + /* So that the DMA channel nodes can be probed individually: */
> + { .compatible = "fsl,eloplus-dma", },
> {}
The removal of gianfar looks a bit surprising here - if it's not a
mistake a note in the changelog would be useful.
^ permalink raw reply
* Re: [PATCH v21 001/100] eclone (1/11): Factor out code to allocate pidmap page
From: David Howells @ 2010-05-04 14:43 UTC (permalink / raw)
To: Oren Laadan
Cc: linux-s390, linux-api, containers, x86, linux-kernel,
linuxppc-dev, Matt Helsley, Serge Hallyn, Andrew Morton,
Sukadev Bhattiprolu, Pavel Emelyanov
In-Reply-To: <1272723382-19470-2-git-send-email-orenl@cs.columbia.edu>
With a huge patch series like this, can you post a cover note at the front
(usually patch 0) saying what the point of the whole series is?
David
^ permalink raw reply
* Re: [PATCH] mpc5121_nfc: Changed SVR check to allow MPC5123.
From: Artem Bityutskiy @ 2010-05-04 13:47 UTC (permalink / raw)
To: Steve Deiters; +Cc: linuxppc-dev, linux-mtd
In-Reply-To: <181804936ABC2349BE503168465576460E95836C@exchserver.basler.com>
On Wed, 2010-04-28 at 17:33 -0500, Steve Deiters wrote:
> The revision in SVR for MPC5123 is 3. The NFC is the same as MPC5121
> revision 2.
>
> ---
> drivers/mtd/nand/mpc5121_nfc.c | 4 ++--
> 1 files changed, 2 insertions(+), 2 deletions(-)
>
Please, add your Signed-off-by and re-send.
--
Best Regards,
Artem Bityutskiy (Артём Битюцкий)
^ permalink raw reply
* [RFC][PATCH 12/12 sample] qemu-kvm: use new API for getting/switching dirty bitmaps
From: Takuya Yoshikawa @ 2010-05-04 13:11 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
We use new API for light dirty log access if KVM supports it.
This conflicts with Marcelo's patches. So please take this as a sample patch.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
---
kvm/include/linux/kvm.h | 11 ++++++
qemu-kvm.c | 81 ++++++++++++++++++++++++++++++++++++++++++-----
qemu-kvm.h | 1 +
3 files changed, 85 insertions(+), 8 deletions(-)
diff --git a/kvm/include/linux/kvm.h b/kvm/include/linux/kvm.h
index 6485981..efd9538 100644
--- a/kvm/include/linux/kvm.h
+++ b/kvm/include/linux/kvm.h
@@ -317,6 +317,14 @@ struct kvm_dirty_log {
};
};
+/* for KVM_GET_USER_DIRTY_LOG_ADDR */
+struct kvm_user_dirty_log {
+ __u32 slot;
+ __u32 flags;
+ __u64 dirty_bitmap;
+ __u64 dirty_bitmap_old;
+};
+
/* for KVM_SET_SIGNAL_MASK */
struct kvm_signal_mask {
__u32 len;
@@ -499,6 +507,7 @@ struct kvm_ioeventfd {
#define KVM_CAP_PPC_SEGSTATE 43
#define KVM_CAP_PCI_SEGMENT 47
+#define KVM_CAP_USER_DIRTY_LOG 55
#ifdef KVM_CAP_IRQ_ROUTING
@@ -595,6 +604,8 @@ struct kvm_clock_data {
struct kvm_userspace_memory_region)
#define KVM_SET_TSS_ADDR _IO(KVMIO, 0x47)
#define KVM_SET_IDENTITY_MAP_ADDR _IOW(KVMIO, 0x48, __u64)
+#define KVM_GET_USER_DIRTY_LOG_ADDR _IOW(KVMIO, 0x49, struct kvm_user_dirty_log)
+#define KVM_SWITCH_DIRTY_LOG _IO(KVMIO, 0x4a)
/* Device model IOC */
#define KVM_CREATE_IRQCHIP _IO(KVMIO, 0x60)
#define KVM_IRQ_LINE _IOW(KVMIO, 0x61, struct kvm_irq_level)
diff --git a/qemu-kvm.c b/qemu-kvm.c
index 91f0222..98777f0 100644
--- a/qemu-kvm.c
+++ b/qemu-kvm.c
@@ -143,6 +143,8 @@ struct slot_info {
unsigned long userspace_addr;
unsigned flags;
int logging_count;
+ unsigned long *dirty_bitmap;
+ unsigned long *dirty_bitmap_old;
};
struct slot_info slots[KVM_MAX_NUM_MEM_REGIONS];
@@ -232,6 +234,29 @@ int kvm_is_containing_region(kvm_context_t kvm, unsigned long phys_addr,
return 1;
}
+static int kvm_user_dirty_log_works(void)
+{
+ return kvm_state->user_dirty_log;
+}
+
+static int kvm_set_user_dirty_log(int slot)
+{
+ int r;
+ struct kvm_user_dirty_log dlog;
+
+ dlog.slot = slot;
+ r = kvm_vm_ioctl(kvm_state, KVM_GET_USER_DIRTY_LOG_ADDR, &dlog);
+ if (r < 0) {
+ DPRINTF("KVM_GET_USER_DIRTY_LOG_ADDR failed: %s\n", strerror(-r));
+ return r;
+ }
+ slots[slot].dirty_bitmap = (unsigned long *)
+ ((unsigned long)dlog.dirty_bitmap);
+ slots[slot].dirty_bitmap_old = (unsigned long *)
+ ((unsigned long)dlog.dirty_bitmap_old);
+ return r;
+}
+
/*
* dirty pages logging control
*/
@@ -265,8 +290,16 @@ static int kvm_dirty_pages_log_change(kvm_context_t kvm,
DPRINTF("slot %d start %llx len %llx flags %x\n",
mem.slot, mem.guest_phys_addr, mem.memory_size, mem.flags);
r = kvm_vm_ioctl(kvm_state, KVM_SET_USER_MEMORY_REGION, &mem);
- if (r < 0)
+ if (r < 0) {
fprintf(stderr, "%s: %m\n", __FUNCTION__);
+ return r;
+ }
+ }
+ if (flags & KVM_MEM_LOG_DIRTY_PAGES) {
+ r = kvm_set_user_dirty_log(slot);
+ } else {
+ slots[slot].dirty_bitmap = NULL;
+ slots[slot].dirty_bitmap_old = NULL;
}
return r;
}
@@ -589,7 +622,6 @@ int kvm_register_phys_mem(kvm_context_t kvm,
unsigned long phys_start, void *userspace_addr,
unsigned long len, int log)
{
-
struct kvm_userspace_memory_region memory = {
.memory_size = len,
.guest_phys_addr = phys_start,
@@ -608,6 +640,9 @@ int kvm_register_phys_mem(kvm_context_t kvm,
fprintf(stderr, "create_userspace_phys_mem: %s\n", strerror(-r));
return -1;
}
+ if (log) {
+ r = kvm_set_user_dirty_log(memory.slot);
+ }
register_slot(memory.slot, memory.guest_phys_addr, memory.memory_size,
memory.userspace_addr, memory.flags);
return 0;
@@ -652,6 +687,8 @@ void kvm_destroy_phys_mem(kvm_context_t kvm, unsigned long phys_start,
fprintf(stderr, "destroy_userspace_phys_mem: %s", strerror(-r));
return;
}
+ slots[memory.slot].dirty_bitmap = NULL;
+ slots[memory.slot].dirty_bitmap_old = NULL;
free_slot(memory.slot);
}
@@ -692,6 +729,21 @@ int kvm_get_dirty_pages(kvm_context_t kvm, unsigned long phys_addr, void *buf)
return kvm_get_map(kvm, KVM_GET_DIRTY_LOG, slot, buf);
}
+static int kvm_switch_map(int slot)
+{
+ int r;
+
+ r = kvm_vm_ioctl(kvm_state, KVM_SWITCH_DIRTY_LOG, slot);
+ if (r == 0) {
+ unsigned long *dirty_bitmap;
+
+ dirty_bitmap = slots[slot].dirty_bitmap;
+ slots[slot].dirty_bitmap = slots[slot].dirty_bitmap_old;
+ slots[slot].dirty_bitmap_old = dirty_bitmap;
+ }
+ return r;
+}
+
int kvm_get_dirty_pages_range(kvm_context_t kvm, unsigned long phys_addr,
unsigned long len, void *opaque,
int (*cb)(unsigned long start,
@@ -706,14 +758,25 @@ int kvm_get_dirty_pages_range(kvm_context_t kvm, unsigned long phys_addr,
for (i = 0; i < KVM_MAX_NUM_MEM_REGIONS; ++i) {
if ((slots[i].len && (uint64_t) slots[i].phys_addr >= phys_addr)
&& ((uint64_t) slots[i].phys_addr + slots[i].len <= end_addr)) {
- buf = qemu_malloc(BITMAP_SIZE(slots[i].len));
- r = kvm_get_map(kvm, KVM_GET_DIRTY_LOG, i, buf);
- if (r) {
+ if (kvm_user_dirty_log_works()) {
+ r = kvm_switch_map(i);
+ if (r == 1) { /* slot was clean */
+ continue;
+ } else if (r < 0) {
+ return r;
+ }
+ r = cb(slots[i].phys_addr, slots[i].len,
+ slots[i].dirty_bitmap_old, opaque);
+ } else {
+ buf = qemu_malloc(BITMAP_SIZE(slots[i].len));
+ r = kvm_get_map(kvm, KVM_GET_DIRTY_LOG, i, buf);
+ if (r) {
+ qemu_free(buf);
+ return r;
+ }
+ r = cb(slots[i].phys_addr, slots[i].len, buf, opaque);
qemu_free(buf);
- return r;
}
- r = cb(slots[i].phys_addr, slots[i].len, buf, opaque);
- qemu_free(buf);
if (r)
return r;
}
@@ -2097,6 +2160,8 @@ static int kvm_create_context(void)
#ifdef TARGET_I386
destroy_region_works = kvm_destroy_memory_region_works(kvm_context);
#endif
+ kvm_state->user_dirty_log
+ = kvm_check_extension(kvm_state, KVM_CAP_USER_DIRTY_LOG);
r = kvm_arch_init_irq_routing();
if (r < 0) {
diff --git a/qemu-kvm.h b/qemu-kvm.h
index ba3808a..aea89df 100644
--- a/qemu-kvm.h
+++ b/qemu-kvm.h
@@ -973,6 +973,7 @@ struct KVMState {
#endif
int irqchip_in_kernel;
int pit_in_kernel;
+ int user_dirty_log;
struct kvm_context kvm_context;
};
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 11/12] KVM: introduce new API for getting/switching dirty bitmaps
From: Takuya Yoshikawa @ 2010-05-04 13:08 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
Now that dirty bitmaps are accessible from user space, we export the
addresses of these to achieve zero-copy dirty log check:
KVM_GET_USER_DIRTY_LOG_ADDR
We also need an API for triggering dirty bitmap switch to take the full
advantage of double buffered bitmaps.
KVM_SWITCH_DIRTY_LOG
See the documentation in this patch for precise explanations.
About performance improvement: the most important feature of switch API
is the lightness. In our test, this appeared in the form of improved
responses for GUI manipulations.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
CC: Avi Kivity <avi@redhat.com>
CC: Alexander Graf <agraf@suse.de>
---
Documentation/kvm/api.txt | 87 +++++++++++++++++++++++++++++++++++++++++++++
arch/ia64/kvm/kvm-ia64.c | 27 +++++++++-----
arch/powerpc/kvm/book3s.c | 18 +++++++--
arch/x86/kvm/x86.c | 44 ++++++++++++++++-------
include/linux/kvm.h | 11 ++++++
include/linux/kvm_host.h | 4 ++-
virt/kvm/kvm_main.c | 63 +++++++++++++++++++++++++++++----
7 files changed, 220 insertions(+), 34 deletions(-)
diff --git a/Documentation/kvm/api.txt b/Documentation/kvm/api.txt
index a237518..c106c83 100644
--- a/Documentation/kvm/api.txt
+++ b/Documentation/kvm/api.txt
@@ -892,6 +892,93 @@ arguments.
This ioctl is only useful after KVM_CREATE_IRQCHIP. Without an in-kernel
irqchip, the multiprocessing state must be maintained by userspace.
+4.39 KVM_GET_USER_DIRTY_LOG_ADDR
+
+Capability: KVM_CAP_USER_DIRTY_LOG (>=1 see below)
+Architectures: all
+Type: vm ioctl
+Parameters: struct kvm_user_dirty_log (in/out)
+Returns: 0 on success, -1 on error
+
+This ioctl makes it possible to use KVM_SWITCH_DIRTY_LOG (see 4.40) instead
+of the old dirty log manipulation by KVM_GET_DIRTY_LOG.
+
+A bit about KVM_CAP_USER_DIRTY_LOG
+
+Before this ioctl was introduced, dirty bitmaps for dirty page logging were
+allocated in the kernel's memory space. But we have now moved them to user
+space to get more flexiblity and performance. To achive this move without
+breaking the compatibility, we will split KVM_CAP_USER_DIRTY_LOG capability
+into a few generations which can be identified by its check extension
+return values.
+
+This KVM_GET_USER_DIRTY_LOG_ADDR belongs to the first generation with the
+KVM_SWITCH_DIRTY_LOG (4.40) and must be supported by all generations.
+
+What you get
+
+By using this, you can get paired bitmap addresses which are accessible from
+user space. See the explanation in 4.40 for the roles of these two bitmaps.
+
+How to Get
+
+Before calling this, you have to set the slot member of kvm_user_dirty_log
+to indicate the target memory slot.
+
+struct kvm_user_dirty_log {
+ __u32 slot;
+ __u32 flags;
+ __u64 dirty_bitmap;
+ __u64 dirty_bitmap_old;
+};
+
+The addresses will be set in the paired members: dirty_bitmap and _old.
+
+Note
+
+In generation 1, we support bitmaps which are created in kernel but do not
+support bitmaps created by users. This means bitmap creation/destruction
+are done same as before when you instruct KVM by KVM_SET_USER_MEMORY_REGION
+(see 4.34) to start/stop logging. Please do not try to free the exported
+bitmaps by yourself, or KVM will access the freed area and end with fault.
+
+4.40 KVM_SWITCH_DIRTY_LOG
+
+Capability: KVM_CAP_USER_DIRTY_LOG (>=1 see 4.39)
+Architectures: all
+Type: vm ioctl
+Parameters: memory slot id
+Returns: 0 if switched, 1 if not (slot was clean), -1 on error
+
+This ioctl allows you to switch the dirty log to the next one: a newer
+ioctl for getting dirty page logs than KVM_GET_DIRTY_LOG (see 4.7 for the
+explanation about dirty page logging, log format is not changed).
+
+If you have the capability KVM_CAP_USER_DIRTY_LOG, using this is strongly
+recommended than using KVM_GET_DIRTY_LOG because this does not need buffer
+copy between kernel and user space.
+
+How to Use
+
+Before calling this, you have to remember the paired addresses of dirty
+bitmaps which can be obtained by KVM_GET_USER_DIRTY_LOG_ADDR (see 4.39):
+dirty_bitmap (being used now by kernel) and dirty_bitmap_old (not being
+used now and containing the last log).
+
+After calling this, the role of these bitmaps will change like this;
+If the return value was 0, kernel cleared dirty_bitmap_old and began to use
+it for the next logging, so that you can use the cold dirty_bitmap to check
+the log since the last switch. If the return value was 1, all pages were not
+dirty and bitmap switch was not done. Note that remembering which bitmap is
+now active is your responsibility. So you have to update your remembering
+when you get the return value 0.
+
+Note
+
+Bitmap switch may also occur when you call KVM_GET_DIRTY_LOG. Please use
+either one, preferably this one, only to avoid extra confusion. We do not
+guarantee on which condition KVM_GET_DIRTY_LOG causes bitmap switch.
+
5. The kvm_run structure
Application code obtains a pointer to the kvm_run structure by
diff --git a/arch/ia64/kvm/kvm-ia64.c b/arch/ia64/kvm/kvm-ia64.c
index 03503e6..b590b80 100644
--- a/arch/ia64/kvm/kvm-ia64.c
+++ b/arch/ia64/kvm/kvm-ia64.c
@@ -1801,8 +1801,7 @@ void kvm_arch_exit(void)
kvm_vmm_info = NULL;
}
-static int kvm_ia64_sync_dirty_log(struct kvm *kvm,
- struct kvm_dirty_log *log)
+static int kvm_ia64_sync_dirty_log(struct kvm *kvm, int slot)
{
struct kvm_memory_slot *memslot;
int r, i;
@@ -1812,10 +1811,10 @@ static int kvm_ia64_sync_dirty_log(struct kvm *kvm,
offsetof(struct kvm_vm_data, kvm_mem_dirty_log));
r = -EINVAL;
- if (log->slot >= KVM_MEMORY_SLOTS)
+ if (slot >= KVM_MEMORY_SLOTS)
goto out;
- memslot = &kvm->memslots->memslots[log->slot];
+ memslot = &kvm->memslots->memslots[slot];
r = -ENOENT;
if (!memslot->dirty_bitmap)
goto out;
@@ -1843,8 +1842,8 @@ out:
return r;
}
-int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
- struct kvm_dirty_log *log)
+static int kvm_ia64_update_dirty_log(struct kvm *kvm, int slot,
+ unsigned long __user *log_bitmap)
{
int r;
unsigned long n;
@@ -1853,15 +1852,15 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
mutex_lock(&kvm->slots_lock);
spin_lock(&kvm->arch.dirty_log_lock);
- r = kvm_ia64_sync_dirty_log(kvm, log);
+ r = kvm_ia64_sync_dirty_log(kvm, slot);
if (r)
goto out;
- r = kvm_get_dirty_log(kvm, log);
+ r = kvm_update_dirty_log(kvm, slot, log_bitmap);
if (r)
goto out;
- memslot = &kvm->memslots->memslots[log->slot];
+ memslot = &kvm->memslots->memslots[slot];
/* If nothing is dirty, don't bother messing with page tables. */
if (memslot->is_dirty) {
kvm_flush_remote_tlbs(kvm);
@@ -1879,6 +1878,16 @@ out:
return r;
}
+int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
+{
+ return kvm_ia64_update_dirty_log(kvm, log->slot, log->dirty_bitmap);
+}
+
+int kvm_vm_ioctl_switch_dirty_log(struct kvm *kvm, int slot)
+{
+ return kvm_ia64_update_dirty_log(kvm, slot, NULL);
+}
+
int kvm_arch_hardware_setup(void)
{
return 0;
diff --git a/arch/powerpc/kvm/book3s.c b/arch/powerpc/kvm/book3s.c
index 2a31d2f..54b3a76 100644
--- a/arch/powerpc/kvm/book3s.c
+++ b/arch/powerpc/kvm/book3s.c
@@ -1185,8 +1185,8 @@ int kvm_arch_vcpu_ioctl_translate(struct kvm_vcpu *vcpu,
/*
* Get (and clear) the dirty memory log for a memory slot.
*/
-int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
- struct kvm_dirty_log *log)
+static int kvmppc_update_dirty_log(struct kvm *kvm, int slot,
+ unsigned long __user *log_bitmap)
{
struct kvm_memory_slot *memslot;
struct kvm_vcpu *vcpu;
@@ -1196,11 +1196,11 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
mutex_lock(&kvm->slots_lock);
- r = kvm_get_dirty_log(kvm, log);
+ r = kvm_update_dirty_log(kvm, slot, log_bitmap);
if (r)
goto out;
- memslot = &kvm->memslots->memslots[log->slot];
+ memslot = &kvm->memslots->memslots[slot];
/* If nothing is dirty, don't bother messing with page tables. */
if (memslot->is_dirty) {
ga = memslot->base_gfn << PAGE_SHIFT;
@@ -1223,6 +1223,16 @@ out:
return r;
}
+int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
+{
+ return kvmppc_update_dirty_log(kvm, log->slot, log->dirty_bitmap);
+}
+
+int kvm_vm_ioctl_switch_dirty_log(struct kvm *kvm, int slot)
+{
+ return kvmppc_update_dirty_log(kvm, slot, NULL);
+}
+
int kvmppc_core_check_processor_compat(void)
{
return 0;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 32a3d94..7a31ab1 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2737,8 +2737,8 @@ static int kvm_vm_ioctl_reinject(struct kvm *kvm,
/*
* Get (and clear) the dirty memory log for a memory slot.
*/
-int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
- struct kvm_dirty_log *log)
+static int kvm_x86_update_dirty_log(struct kvm *kvm, int slot,
+ unsigned long __user *log_bitmap)
{
int r;
struct kvm_memory_slot *memslot;
@@ -2747,10 +2747,10 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
mutex_lock(&kvm->slots_lock);
r = -EINVAL;
- if (log->slot >= KVM_MEMORY_SLOTS)
+ if (slot >= KVM_MEMORY_SLOTS)
goto out;
- memslot = &kvm->memslots->memslots[log->slot];
+ memslot = &kvm->memslots->memslots[slot];
r = -ENOENT;
if (!memslot->dirty_bitmap)
goto out;
@@ -2764,7 +2764,7 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
unsigned long __user *dirty_bitmap_old;
spin_lock(&kvm->mmu_lock);
- kvm_mmu_slot_remove_write_access(kvm, log->slot);
+ kvm_mmu_slot_remove_write_access(kvm, slot);
spin_unlock(&kvm->mmu_lock);
dirty_bitmap = memslot->dirty_bitmap;
@@ -2779,22 +2779,30 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
goto out;
memcpy(slots, kvm->memslots, sizeof(struct kvm_memslots));
- slots->memslots[log->slot].dirty_bitmap = dirty_bitmap_old;
- slots->memslots[log->slot].dirty_bitmap_old = dirty_bitmap;
- slots->memslots[log->slot].is_dirty = false;
+ slots->memslots[slot].dirty_bitmap = dirty_bitmap_old;
+ slots->memslots[slot].dirty_bitmap_old = dirty_bitmap;
+ slots->memslots[slot].is_dirty = false;
old_slots = kvm->memslots;
rcu_assign_pointer(kvm->memslots, slots);
synchronize_srcu_expedited(&kvm->srcu);
kfree(old_slots);
- r = -EFAULT;
- if (copy_in_user(log->dirty_bitmap, dirty_bitmap, n))
- goto out;
+ if (log_bitmap) {
+ r = -EFAULT;
+ if (copy_in_user(log_bitmap, dirty_bitmap, n))
+ goto out;
+ }
} else {
- r = -EFAULT;
- if (clear_user(log->dirty_bitmap, n))
+ if (log_bitmap) {
+ r = -EFAULT;
+ if (clear_user(log_bitmap, n))
+ goto out;
+ } else {
+ /* Tell the user about no switch. */
+ r = 1;
goto out;
+ }
}
r = 0;
@@ -2803,6 +2811,16 @@ out:
return r;
}
+int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
+{
+ return kvm_x86_update_dirty_log(kvm, log->slot, log->dirty_bitmap);
+}
+
+int kvm_vm_ioctl_switch_dirty_log(struct kvm *kvm, int slot)
+{
+ return kvm_x86_update_dirty_log(kvm, slot, NULL);
+}
+
long kvm_arch_vm_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
diff --git a/include/linux/kvm.h b/include/linux/kvm.h
index 23ea022..47980c2 100644
--- a/include/linux/kvm.h
+++ b/include/linux/kvm.h
@@ -322,6 +322,14 @@ struct kvm_dirty_log {
};
};
+/* for KVM_GET_USER_DIRTY_LOG_ADDR */
+struct kvm_user_dirty_log {
+ __u32 slot;
+ __u32 flags;
+ __u64 dirty_bitmap;
+ __u64 dirty_bitmap_old;
+};
+
/* for KVM_SET_SIGNAL_MASK */
struct kvm_signal_mask {
__u32 len;
@@ -524,6 +532,7 @@ struct kvm_enable_cap {
#define KVM_CAP_PPC_OSI 52
#define KVM_CAP_PPC_UNSET_IRQ 53
#define KVM_CAP_ENABLE_CAP 54
+#define KVM_CAP_USER_DIRTY_LOG 55
#ifdef KVM_CAP_IRQ_ROUTING
@@ -620,6 +629,8 @@ struct kvm_clock_data {
struct kvm_userspace_memory_region)
#define KVM_SET_TSS_ADDR _IO(KVMIO, 0x47)
#define KVM_SET_IDENTITY_MAP_ADDR _IOW(KVMIO, 0x48, __u64)
+#define KVM_GET_USER_DIRTY_LOG_ADDR _IOW(KVMIO, 0x49, struct kvm_user_dirty_log)
+#define KVM_SWITCH_DIRTY_LOG _IO(KVMIO, 0x4a)
/* Device model IOC */
#define KVM_CREATE_IRQCHIP _IO(KVMIO, 0x60)
#define KVM_IRQ_LINE _IOW(KVMIO, 0x61, struct kvm_irq_level)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index c95e2b7..a94277a 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -337,9 +337,11 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
int kvm_dev_ioctl_check_extension(long ext);
-int kvm_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log);
+int kvm_update_dirty_log(struct kvm *kvm, int slot,
+ unsigned long __user *log_bitmap);
int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
struct kvm_dirty_log *log);
+int kvm_vm_ioctl_switch_dirty_log(struct kvm *kvm, int slot);
int kvm_vm_ioctl_set_memory_region(struct kvm *kvm,
struct
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index ddcf65a..300a0c1 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -818,26 +818,55 @@ int kvm_vm_ioctl_set_memory_region(struct kvm *kvm,
return kvm_set_memory_region(kvm, mem, user_alloc);
}
-int kvm_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
+static int kvm_vm_ioctl_get_user_dirty_log_addr(struct kvm *kvm,
+ struct kvm_user_dirty_log *log)
+{
+ struct kvm_memory_slot *memslot;
+
+ if (log->slot >= KVM_MEMORY_SLOTS)
+ return -EINVAL;
+
+ memslot = &kvm->memslots->memslots[log->slot];
+ if (!memslot->dirty_bitmap)
+ return -ENOENT;
+
+ log->dirty_bitmap = (unsigned long)memslot->dirty_bitmap;
+ log->dirty_bitmap_old = (unsigned long)memslot->dirty_bitmap_old;
+ return 0;
+}
+
+int kvm_update_dirty_log(struct kvm *kvm, int slot,
+ unsigned long __user *log_bitmap)
{
struct kvm_memory_slot *memslot;
int r;
- unsigned long n;
r = -EINVAL;
- if (log->slot >= KVM_MEMORY_SLOTS)
+ if (slot >= KVM_MEMORY_SLOTS)
goto out;
- memslot = &kvm->memslots->memslots[log->slot];
+ memslot = &kvm->memslots->memslots[slot];
r = -ENOENT;
if (!memslot->dirty_bitmap)
goto out;
- n = kvm_dirty_bitmap_bytes(memslot);
+ if (log_bitmap) {
+ unsigned long n = kvm_dirty_bitmap_bytes(memslot);
- r = -EFAULT;
- if (copy_in_user(log->dirty_bitmap, memslot->dirty_bitmap, n))
+ r = -EFAULT;
+ if (copy_in_user(log_bitmap, memslot->dirty_bitmap, n))
+ goto out;
+ } else if (memslot->is_dirty) {
+ unsigned long __user *dirty_bitmap;
+
+ dirty_bitmap = memslot->dirty_bitmap;
+ memslot->dirty_bitmap = memslot->dirty_bitmap_old;
+ memslot->dirty_bitmap_old = dirty_bitmap;
+ } else {
+ /* Tell the user about no switch. */
+ r = 1;
goto out;
+ }
r = 0;
out:
@@ -1647,6 +1676,25 @@ static long kvm_vm_ioctl(struct file *filp,
goto out;
break;
}
+ case KVM_GET_USER_DIRTY_LOG_ADDR: {
+ struct kvm_user_dirty_log log;
+
+ r = -EFAULT;
+ if (copy_from_user(&log, argp, sizeof log))
+ goto out;
+ r = kvm_vm_ioctl_get_user_dirty_log_addr(kvm, &log);
+ if (r)
+ goto out;
+ r = -EFAULT;
+ if (copy_to_user(argp, &log, sizeof log))
+ goto out;
+ r = 0;
+ break;
+ }
+ case KVM_SWITCH_DIRTY_LOG: {
+ r = kvm_vm_ioctl_switch_dirty_log(kvm, arg);
+ break;
+ }
#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
case KVM_REGISTER_COALESCED_MMIO: {
struct kvm_coalesced_mmio_zone zone;
@@ -1823,6 +1871,7 @@ static long kvm_dev_ioctl_check_extension_generic(long arg)
case KVM_CAP_USER_MEMORY:
case KVM_CAP_DESTROY_MEMORY_REGION_WORKS:
case KVM_CAP_JOIN_MEMORY_REGIONS_WORKS:
+ case KVM_CAP_USER_DIRTY_LOG:
#ifdef CONFIG_KVM_APIC_ARCHITECTURE
case KVM_CAP_SET_BOOT_CPU_ID:
#endif
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH RFC 10/12] KVM: move dirty bitmaps to user space
From: Takuya Yoshikawa @ 2010-05-04 13:07 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
We move dirty bitmaps to user space.
- Allocation and destruction: we use do_mmap() and do_munmap().
The new bitmap space is twice longer than the original one and we
use the additional space for double buffering: this makes it
possible to update the active bitmap while letting the user space
read the other one safely. For x86, we can also remove the vmalloc()
in kvm_vm_ioctl_get_dirty_log().
- Bitmap manipulations: we replace all functions which access dirty
bitmaps with *_user() functions.
- For ia64: moving the dirty bitmaps of memory slots does not affect
ia64 much because it's using a different place to store dirty logs
rather than the dirty bitmaps of memory slots: all we have to change
are sync and get of dirty log, so we don't need set_bit_user like
functions for ia64.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
CC: Avi Kivity <avi@redhat.com>
CC: Alexander Graf <agraf@suse.de>
---
arch/ia64/kvm/kvm-ia64.c | 15 +++++++++-
arch/powerpc/kvm/book3s.c | 5 +++-
arch/x86/kvm/x86.c | 25 ++++++++----------
include/linux/kvm_host.h | 3 +-
virt/kvm/kvm_main.c | 62 +++++++++++++++++++++++++++++++++++++-------
5 files changed, 82 insertions(+), 28 deletions(-)
diff --git a/arch/ia64/kvm/kvm-ia64.c b/arch/ia64/kvm/kvm-ia64.c
index 17fd65c..03503e6 100644
--- a/arch/ia64/kvm/kvm-ia64.c
+++ b/arch/ia64/kvm/kvm-ia64.c
@@ -1823,11 +1823,19 @@ static int kvm_ia64_sync_dirty_log(struct kvm *kvm,
n = kvm_dirty_bitmap_bytes(memslot);
base = memslot->base_gfn / BITS_PER_LONG;
+ r = -EFAULT;
+ if (!access_ok(VERIFY_WRITE, memslot->dirty_bitmap, n))
+ goto out;
+
for (i = 0; i < n/sizeof(long); ++i) {
if (dirty_bitmap[base + i])
memslot->is_dirty = true;
- memslot->dirty_bitmap[i] = dirty_bitmap[base + i];
+ if (__put_user(dirty_bitmap[base + i],
+ &memslot->dirty_bitmap[i])) {
+ r = -EFAULT;
+ goto out;
+ }
dirty_bitmap[base + i] = 0;
}
r = 0;
@@ -1858,7 +1866,10 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
if (memslot->is_dirty) {
kvm_flush_remote_tlbs(kvm);
n = kvm_dirty_bitmap_bytes(memslot);
- memset(memslot->dirty_bitmap, 0, n);
+ if (clear_user(memslot->dirty_bitmap, n)) {
+ r = -EFAULT;
+ goto out;
+ }
memslot->is_dirty = false;
}
r = 0;
diff --git a/arch/powerpc/kvm/book3s.c b/arch/powerpc/kvm/book3s.c
index 4b074f1..2a31d2f 100644
--- a/arch/powerpc/kvm/book3s.c
+++ b/arch/powerpc/kvm/book3s.c
@@ -1210,7 +1210,10 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
kvmppc_mmu_pte_pflush(vcpu, ga, ga_end);
n = kvm_dirty_bitmap_bytes(memslot);
- memset(memslot->dirty_bitmap, 0, n);
+ if (clear_user(memslot->dirty_bitmap, n)) {
+ r = -EFAULT;
+ goto out;
+ }
memslot->is_dirty = false;
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 023c7f8..32a3d94 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2760,40 +2760,37 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
/* If nothing is dirty, don't bother messing with page tables. */
if (memslot->is_dirty) {
struct kvm_memslots *slots, *old_slots;
- unsigned long *dirty_bitmap;
+ unsigned long __user *dirty_bitmap;
+ unsigned long __user *dirty_bitmap_old;
spin_lock(&kvm->mmu_lock);
kvm_mmu_slot_remove_write_access(kvm, log->slot);
spin_unlock(&kvm->mmu_lock);
- r = -ENOMEM;
- dirty_bitmap = vmalloc(n);
- if (!dirty_bitmap)
+ dirty_bitmap = memslot->dirty_bitmap;
+ dirty_bitmap_old = memslot->dirty_bitmap_old;
+ r = -EFAULT;
+ if (clear_user(dirty_bitmap_old, n))
goto out;
- memset(dirty_bitmap, 0, n);
r = -ENOMEM;
slots = kzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
- if (!slots) {
- vfree(dirty_bitmap);
+ if (!slots)
goto out;
- }
+
memcpy(slots, kvm->memslots, sizeof(struct kvm_memslots));
- slots->memslots[log->slot].dirty_bitmap = dirty_bitmap;
+ slots->memslots[log->slot].dirty_bitmap = dirty_bitmap_old;
+ slots->memslots[log->slot].dirty_bitmap_old = dirty_bitmap;
slots->memslots[log->slot].is_dirty = false;
old_slots = kvm->memslots;
rcu_assign_pointer(kvm->memslots, slots);
synchronize_srcu_expedited(&kvm->srcu);
- dirty_bitmap = old_slots->memslots[log->slot].dirty_bitmap;
kfree(old_slots);
r = -EFAULT;
- if (copy_to_user(log->dirty_bitmap, dirty_bitmap, n)) {
- vfree(dirty_bitmap);
+ if (copy_in_user(log->dirty_bitmap, dirty_bitmap, n))
goto out;
- }
- vfree(dirty_bitmap);
} else {
r = -EFAULT;
if (clear_user(log->dirty_bitmap, n))
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 0aa6ecb..c95e2b7 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -116,7 +116,8 @@ struct kvm_memory_slot {
unsigned long npages;
unsigned long flags;
unsigned long *rmap;
- unsigned long *dirty_bitmap;
+ unsigned long __user *dirty_bitmap;
+ unsigned long __user *dirty_bitmap_old;
bool is_dirty;
struct {
unsigned long rmap_pde;
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 3e3acad..ddcf65a 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -437,8 +437,20 @@ out_err_nodisable:
static void kvm_destroy_dirty_bitmap(struct kvm_memory_slot *memslot)
{
- vfree(memslot->dirty_bitmap);
+ unsigned long user_addr;
+ unsigned long n = kvm_dirty_bitmap_bytes(memslot);
+
+ if (!memslot->dirty_bitmap)
+ return;
+
+ user_addr = min((unsigned long)memslot->dirty_bitmap,
+ (unsigned long)memslot->dirty_bitmap_old);
+ down_write(¤t->mm->mmap_sem);
+ do_munmap(current->mm, user_addr, 2 * n);
+ up_write(¤t->mm->mmap_sem);
+
memslot->dirty_bitmap = NULL;
+ memslot->dirty_bitmap_old = NULL;
}
/*
@@ -472,8 +484,12 @@ void kvm_free_physmem(struct kvm *kvm)
int i;
struct kvm_memslots *slots = kvm->memslots;
- for (i = 0; i < slots->nmemslots; ++i)
+ for (i = 0; i < slots->nmemslots; ++i) {
+ /* VM process will exit: we don't unmap by ourselves. */
+ slots->memslots[i].dirty_bitmap = NULL;
+ slots->memslots[i].dirty_bitmap_old = NULL;
kvm_free_physmem_slot(&slots->memslots[i], NULL);
+ }
kfree(kvm->memslots);
}
@@ -527,14 +543,35 @@ static int kvm_vm_release(struct inode *inode, struct file *filp)
static int kvm_create_dirty_bitmap(struct kvm_memory_slot *memslot)
{
- unsigned long dirty_bytes = kvm_dirty_bitmap_bytes(memslot);
+ int err;
+ unsigned long user_addr;
+ unsigned long n = kvm_dirty_bitmap_bytes(memslot);
- memslot->dirty_bitmap = vmalloc(dirty_bytes);
- if (!memslot->dirty_bitmap)
- return -ENOMEM;
+ down_write(¤t->mm->mmap_sem);
+ user_addr = do_mmap(NULL, 0, 2 * n,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, 0);
+ up_write(¤t->mm->mmap_sem);
+
+ if (IS_ERR((void *)user_addr)) {
+ err = PTR_ERR((void *)user_addr);
+ goto out;
+ }
+
+ memslot->dirty_bitmap = (unsigned long __user *)user_addr;
+ memslot->dirty_bitmap_old = (unsigned long __user *)(user_addr + n);
+ if (clear_user(memslot->dirty_bitmap, 2 * n)) {
+ err = -EFAULT;
+ goto out_unmap;
+ }
- memset(memslot->dirty_bitmap, 0, dirty_bytes);
return 0;
+out_unmap:
+ down_write(¤t->mm->mmap_sem);
+ do_munmap(current->mm, user_addr, 2 * n);
+ up_write(¤t->mm->mmap_sem);
+out:
+ return err;
}
/*
@@ -799,7 +836,7 @@ int kvm_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
n = kvm_dirty_bitmap_bytes(memslot);
r = -EFAULT;
- if (copy_to_user(log->dirty_bitmap, memslot->dirty_bitmap, n))
+ if (copy_in_user(log->dirty_bitmap, memslot->dirty_bitmap, n))
goto out;
r = 0;
@@ -1195,11 +1232,16 @@ void mark_page_dirty(struct kvm *kvm, gfn_t gfn)
gfn = unalias_gfn(kvm, gfn);
memslot = gfn_to_memslot_unaliased(kvm, gfn);
if (memslot && memslot->dirty_bitmap) {
- unsigned long rel_gfn = gfn - memslot->base_gfn;
+ int nr = generic_le_bit_offset(gfn - memslot->base_gfn);
- generic___set_le_bit(rel_gfn, memslot->dirty_bitmap);
+ if (kvm_set_bit_user(nr, memslot->dirty_bitmap))
+ goto out_fault;
memslot->is_dirty = true;
}
+ return;
+
+out_fault:
+ printk(KERN_WARNING "%s: kvm_set_bit_user failed.\n", __func__);
}
/*
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 9/12] KVM: introduce a wrapper function of set_bit_user_non_atomic()
From: Takuya Yoshikawa @ 2010-05-04 13:06 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
This is not to break the build for other architectures than x86 and ppc.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
---
arch/ia64/include/asm/kvm_host.h | 5 +++++
arch/powerpc/include/asm/kvm_host.h | 6 ++++++
arch/s390/include/asm/kvm_host.h | 6 ++++++
arch/x86/include/asm/kvm_host.h | 5 +++++
4 files changed, 22 insertions(+), 0 deletions(-)
diff --git a/arch/ia64/include/asm/kvm_host.h b/arch/ia64/include/asm/kvm_host.h
index a362e67..938041b 100644
--- a/arch/ia64/include/asm/kvm_host.h
+++ b/arch/ia64/include/asm/kvm_host.h
@@ -589,6 +589,11 @@ int kvm_emulate_halt(struct kvm_vcpu *vcpu);
int kvm_pal_emul(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run);
void kvm_sal_emul(struct kvm_vcpu *vcpu);
+static inline int kvm_set_bit_user(int nr, void __user *addr)
+{
+ return 0;
+}
+
#endif /* __ASSEMBLY__*/
#endif
diff --git a/arch/powerpc/include/asm/kvm_host.h b/arch/powerpc/include/asm/kvm_host.h
index 0c9ad86..9463524 100644
--- a/arch/powerpc/include/asm/kvm_host.h
+++ b/arch/powerpc/include/asm/kvm_host.h
@@ -26,6 +26,7 @@
#include <linux/types.h>
#include <linux/kvm_types.h>
#include <asm/kvm_asm.h>
+#include <asm/uaccess.h>
#define KVM_MAX_VCPUS 1
#define KVM_MEMORY_SLOTS 32
@@ -287,4 +288,9 @@ struct kvm_vcpu_arch {
#endif
};
+static inline int kvm_set_bit_user(int nr, void __user *addr)
+{
+ return set_bit_user_non_atomic(nr, addr);
+}
+
#endif /* __POWERPC_KVM_HOST_H__ */
diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h
index 27605b6..36710ee 100644
--- a/arch/s390/include/asm/kvm_host.h
+++ b/arch/s390/include/asm/kvm_host.h
@@ -238,4 +238,10 @@ struct kvm_arch{
};
extern int sie64a(struct kvm_s390_sie_block *, unsigned long *);
+
+static inline int kvm_set_bit_user(int nr, void __user *addr)
+{
+ return 0;
+}
+
#endif
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 3f0007b..9e22df9 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -795,4 +795,9 @@ void kvm_set_shared_msr(unsigned index, u64 val, u64 mask);
bool kvm_is_linear_rip(struct kvm_vcpu *vcpu, unsigned long linear_rip);
+static inline int kvm_set_bit_user(int nr, void __user *addr)
+{
+ return set_bit_user_non_atomic(nr, addr);
+}
+
#endif /* _ASM_X86_KVM_HOST_H */
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH resend 8/12] asm-generic: bitops: introduce le bit offset macro
From: Takuya Yoshikawa @ 2010-05-04 13:05 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
Although we can use *_le_bit() helpers to treat bitmaps le arranged,
having le bit offset calculation as a seperate macro gives us more freedom.
For example, KVM has le arranged dirty bitmaps for VGA, live-migration
and they are used in user space too. To avoid bitmap copies between kernel
and user space, we want to update the bitmaps in user space directly.
To achive this, le bit offset with *_user() functions help us a lot.
So let us use the le bit offset calculation part by defining it as a new
macro: generic_le_bit_offset() .
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
CC: Arnd Bergmann <arnd@arndb.de>
---
include/asm-generic/bitops/le.h | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/include/asm-generic/bitops/le.h b/include/asm-generic/bitops/le.h
index 80e3bf1..ee445fb 100644
--- a/include/asm-generic/bitops/le.h
+++ b/include/asm-generic/bitops/le.h
@@ -9,6 +9,8 @@
#if defined(__LITTLE_ENDIAN)
+#define generic_le_bit_offset(nr) (nr)
+
#define generic_test_le_bit(nr, addr) test_bit(nr, addr)
#define generic___set_le_bit(nr, addr) __set_bit(nr, addr)
#define generic___clear_le_bit(nr, addr) __clear_bit(nr, addr)
@@ -25,6 +27,8 @@
#elif defined(__BIG_ENDIAN)
+#define generic_le_bit_offset(nr) ((nr) ^ BITOP_LE_SWIZZLE)
+
#define generic_test_le_bit(nr, addr) \
test_bit((nr) ^ BITOP_LE_SWIZZLE, (addr))
#define generic___set_le_bit(nr, addr) \
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 7/12 not tested yet] PPC: introduce __set_bit() like function for bitmaps in user space
From: Takuya Yoshikawa @ 2010-05-04 13:04 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
During the work of KVM's dirty page logging optimization, we encountered
the need of manipulating bitmaps in user space efficiantly. To achive this,
we introduce a uaccess function for setting a bit in user space following
Avi's suggestion.
KVM is now using dirty bitmaps for live-migration and VGA. Although we need
to update them from kernel side, copying them every time for updating the
dirty log is a big bottleneck. Especially, we tested that zero-copy bitmap
manipulation improves responses of GUI manipulations a lot.
We also found one similar need in drivers/vhost/vhost.c in which the author
implemented set_bit_to_user() locally using inefficient functions: see TODO
at the top of that.
Probably, this kind of need would be common for virtualization area.
So we introduce a function set_bit_user_non_atomic().
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
CC: Alexander Graf <agraf@suse.de>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
---
arch/powerpc/include/asm/uaccess.h | 19 +++++++++++++++++++
1 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/arch/powerpc/include/asm/uaccess.h b/arch/powerpc/include/asm/uaccess.h
index 3a01ce8..f878326 100644
--- a/arch/powerpc/include/asm/uaccess.h
+++ b/arch/powerpc/include/asm/uaccess.h
@@ -321,6 +321,25 @@ do { \
__gu_err; \
})
+static inline int set_bit_user_non_atomic(int nr, void __user *addr)
+{
+ u8 __user *p;
+ u8 val;
+
+ p = (u8 __user *)((unsigned long)addr + nr / BITS_PER_BYTE);
+ if (!access_ok(VERIFY_WRITE, p, 1))
+ return -EFAULT;
+
+ if (__get_user(val, p))
+ return -EFAULT;
+
+ val |= 1U << (nr % BITS_PER_BYTE);
+ if (__put_user(val, p))
+ return -EFAULT;
+
+ return 0;
+}
+
/* more complex routines */
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 6/12 not tested yet] PPC: introduce copy_in_user() for 32-bit
From: Takuya Yoshikawa @ 2010-05-04 13:03 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
During the work of KVM's dirty page logging optimization, we encountered
the need of copy_in_user() for 32-bit ppc and x86: these will be used for
manipulating dirty bitmaps in user space.
So we implement copy_in_user() for 32-bit with __copy_tofrom_user().
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
CC: Alexander Graf <agraf@suse.de>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
---
arch/powerpc/include/asm/uaccess.h | 17 +++++++++++++++++
1 files changed, 17 insertions(+), 0 deletions(-)
diff --git a/arch/powerpc/include/asm/uaccess.h b/arch/powerpc/include/asm/uaccess.h
index bd0fb84..3a01ce8 100644
--- a/arch/powerpc/include/asm/uaccess.h
+++ b/arch/powerpc/include/asm/uaccess.h
@@ -359,6 +359,23 @@ static inline unsigned long copy_to_user(void __user *to,
return n;
}
+static inline unsigned long copy_in_user(void __user *to,
+ const void __user *from, unsigned long n)
+{
+ unsigned long over;
+
+ if (likely(access_ok(VERIFY_READ, from, n) &&
+ access_ok(VERIFY_WRITE, to, n)))
+ return __copy_tofrom_user(to, from, n);
+ if (((unsigned long)from < TASK_SIZE) ||
+ ((unsigned long)to < TASK_SIZE)) {
+ over = max((unsigned long)from, (unsigned long)to)
+ + n - TASK_SIZE;
+ return __copy_tofrom_user(to, from, n - over) + over;
+ }
+ return n;
+}
+
#else /* __powerpc64__ */
#define __copy_in_user(to, from, size) \
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 5/12] x86: introduce __set_bit() like function for bitmaps in user space
From: Takuya Yoshikawa @ 2010-05-04 13:02 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
During the work of KVM's dirty page logging optimization, we encountered
the need of manipulating bitmaps in user space efficiantly. To achive this,
we introduce a uaccess function for setting a bit in user space following
Avi's suggestion.
KVM is now using dirty bitmaps for live-migration and VGA. Although we need
to update them from kernel side, copying them every time for updating the
dirty log is a big bottleneck. Especially, we tested that zero-copy bitmap
manipulation improves responses of GUI manipulations a lot.
We also found one similar need in drivers/vhost/vhost.c in which the author
implemented set_bit_to_user() locally using inefficient functions: see TODO
at the top of that.
Probably, this kind of need would be common for virtualization area.
So we introduce a macro set_bit_user_non_atomic() following the implementation
style of x86's uaccess functions.
Note: there is a one restriction to this macro: bitmaps must be 64-bit
aligned (see the comment in this patch).
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
CC: Avi Kivity <avi@redhat.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
CC: Ingo Molnar <mingo@redhat.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
---
arch/x86/include/asm/uaccess.h | 39 +++++++++++++++++++++++++++++++++++++++
1 files changed, 39 insertions(+), 0 deletions(-)
diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h
index abd3e0e..3138e65 100644
--- a/arch/x86/include/asm/uaccess.h
+++ b/arch/x86/include/asm/uaccess.h
@@ -98,6 +98,45 @@ struct exception_table_entry {
extern int fixup_exception(struct pt_regs *regs);
+/**
+ * set_bit_user_non_atomic: - set a bit of a bitmap in user space.
+ * @nr: Bit offset.
+ * @addr: Base address of a bitmap in user space.
+ *
+ * Context: User context only. This function may sleep.
+ *
+ * This macro set a bit of a bitmap in user space.
+ *
+ * Restriction: the bitmap pointed to by @addr must be 64-bit aligned:
+ * the kernel accesses the bitmap by its own word length, so bitmaps
+ * allocated by 32-bit processes may cause fault.
+ *
+ * Returns zero on success, or -EFAULT on error.
+ */
+#define __set_bit_user_non_atomic_asm(nr, addr, err, errret) \
+ asm volatile("1: bts %1,%2\n" \
+ "2:\n" \
+ ".section .fixup,\"ax\"\n" \
+ "3: mov %3,%0\n" \
+ " jmp 2b\n" \
+ ".previous\n" \
+ _ASM_EXTABLE(1b, 3b) \
+ : "=r"(err) \
+ : "r" (nr), "m" (__m(addr)), "i" (errret), "0" (err))
+
+#define set_bit_user_non_atomic(nr, addr) \
+({ \
+ int __ret_sbu; \
+ \
+ might_fault(); \
+ if (access_ok(VERIFY_WRITE, addr, nr/8 + 1)) \
+ __set_bit_user_non_atomic_asm(nr, addr, __ret_sbu, -EFAULT);\
+ else \
+ __ret_sbu = -EFAULT; \
+ \
+ __ret_sbu; \
+})
+
/*
* These are the main single-value transfer routines. They automatically
* use the right size if we just have the right pointer type.
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 4/12] x86: introduce copy_in_user() for 32-bit
From: Takuya Yoshikawa @ 2010-05-04 13:02 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
During the work of KVM's dirty page logging optimization, we encountered
the need of copy_in_user() for 32-bit x86 and ppc: these will be used for
manipulating dirty bitmaps in user space.
So we implement copy_in_user() for 32-bit with existing generic copy user
helpers.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
CC: Avi Kivity <avi@redhat.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
CC: Ingo Molnar <mingo@redhat.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
---
arch/x86/include/asm/uaccess_32.h | 2 ++
arch/x86/lib/usercopy_32.c | 26 ++++++++++++++++++++++++++
2 files changed, 28 insertions(+), 0 deletions(-)
diff --git a/arch/x86/include/asm/uaccess_32.h b/arch/x86/include/asm/uaccess_32.h
index 088d09f..85d396d 100644
--- a/arch/x86/include/asm/uaccess_32.h
+++ b/arch/x86/include/asm/uaccess_32.h
@@ -21,6 +21,8 @@ unsigned long __must_check __copy_from_user_ll_nocache
(void *to, const void __user *from, unsigned long n);
unsigned long __must_check __copy_from_user_ll_nocache_nozero
(void *to, const void __user *from, unsigned long n);
+unsigned long __must_check copy_in_user
+ (void __user *to, const void __user *from, unsigned n);
/**
* __copy_to_user_inatomic: - Copy a block of data into user space, with less checking.
diff --git a/arch/x86/lib/usercopy_32.c b/arch/x86/lib/usercopy_32.c
index e218d5d..e90ffc3 100644
--- a/arch/x86/lib/usercopy_32.c
+++ b/arch/x86/lib/usercopy_32.c
@@ -889,3 +889,29 @@ void copy_from_user_overflow(void)
WARN(1, "Buffer overflow detected!\n");
}
EXPORT_SYMBOL(copy_from_user_overflow);
+
+/**
+ * copy_in_user: - Copy a block of data from user space to user space.
+ * @to: Destination address, in user space.
+ * @from: Source address, in user space.
+ * @n: Number of bytes to copy.
+ *
+ * Context: User context only. This function may sleep.
+ *
+ * Copy data from user space to user space.
+ *
+ * Returns number of bytes that could not be copied.
+ * On success, this will be zero.
+ */
+unsigned long
+copy_in_user(void __user *to, const void __user *from, unsigned n)
+{
+ if (access_ok(VERIFY_WRITE, to, n) && access_ok(VERIFY_READ, from, n)) {
+ if (movsl_is_ok(to, from, n))
+ __copy_user(to, from, n);
+ else
+ n = __copy_user_intel(to, (const void *)from, n);
+ }
+ return n;
+}
+EXPORT_SYMBOL(copy_in_user);
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 3/12] KVM: introduce wrapper functions to create and destroy dirty bitmaps
From: Takuya Yoshikawa @ 2010-05-04 13:01 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
We will change the vmalloc() and vfree() to do_mmap() and do_munmap() later.
This patch makes it easy and cleanup the code.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
---
virt/kvm/kvm_main.c | 27 ++++++++++++++++++++-------
1 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 7ab6312..3e3acad 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -435,6 +435,12 @@ out_err_nodisable:
return ERR_PTR(r);
}
+static void kvm_destroy_dirty_bitmap(struct kvm_memory_slot *memslot)
+{
+ vfree(memslot->dirty_bitmap);
+ memslot->dirty_bitmap = NULL;
+}
+
/*
* Free any memory in @free but not in @dont.
*/
@@ -447,7 +453,7 @@ static void kvm_free_physmem_slot(struct kvm_memory_slot *free,
vfree(free->rmap);
if (!dont || free->dirty_bitmap != dont->dirty_bitmap)
- vfree(free->dirty_bitmap);
+ kvm_destroy_dirty_bitmap(free);
for (i = 0; i < KVM_NR_PAGE_SIZES - 1; ++i) {
@@ -458,7 +464,6 @@ static void kvm_free_physmem_slot(struct kvm_memory_slot *free,
}
free->npages = 0;
- free->dirty_bitmap = NULL;
free->rmap = NULL;
}
@@ -520,6 +525,18 @@ static int kvm_vm_release(struct inode *inode, struct file *filp)
return 0;
}
+static int kvm_create_dirty_bitmap(struct kvm_memory_slot *memslot)
+{
+ unsigned long dirty_bytes = kvm_dirty_bitmap_bytes(memslot);
+
+ memslot->dirty_bitmap = vmalloc(dirty_bytes);
+ if (!memslot->dirty_bitmap)
+ return -ENOMEM;
+
+ memset(memslot->dirty_bitmap, 0, dirty_bytes);
+ return 0;
+}
+
/*
* Allocate some memory and give it an address in the guest physical address
* space.
@@ -653,12 +670,8 @@ skip_lpage:
/* Allocate page dirty bitmap if needed */
if ((new.flags & KVM_MEM_LOG_DIRTY_PAGES) && !new.dirty_bitmap) {
- unsigned long dirty_bytes = kvm_dirty_bitmap_bytes(&new);
-
- new.dirty_bitmap = vmalloc(dirty_bytes);
- if (!new.dirty_bitmap)
+ if (kvm_create_dirty_bitmap(&new) < 0)
goto out_free;
- memset(new.dirty_bitmap, 0, dirty_bytes);
/* destroy any largepage mappings for dirty tracking */
if (old.npages)
flush_shadow = 1;
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 2/12] KVM: introduce slot level dirty state management
From: Takuya Yoshikawa @ 2010-05-04 13:00 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
This patch introduces is_dirty member for each memory slot.
Using this member, we remove the dirty bitmap scans which are done in
get_dirty_log().
This is important for moving dirty bitmaps to user space because we don't
have any good ways to check bitmaps in user space with low cost and scanning
bitmaps to check memory slot dirtiness will not be acceptable.
When we mark a slot dirty:
- x86 and ppc: at the timing of mark_page_dirty()
- ia64: at the timing of kvm_ia64_sync_dirty_log()
ia64 uses a different place to store dirty logs and synchronize it with
the logs of memory slots right before the get_dirty_log(). So we use this
timing to update is_dirty.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
Signed-off-by: Fernando Luis Vazquez Cao <fernando@oss.ntt.co.jp>
CC: Avi Kivity <avi@redhat.com>
CC: Alexander Graf <agraf@suse.de>
---
arch/ia64/kvm/kvm-ia64.c | 11 +++++++----
arch/powerpc/kvm/book3s.c | 9 ++++-----
arch/x86/kvm/x86.c | 9 +++------
include/linux/kvm_host.h | 4 ++--
virt/kvm/kvm_main.c | 13 +++----------
5 files changed, 19 insertions(+), 27 deletions(-)
diff --git a/arch/ia64/kvm/kvm-ia64.c b/arch/ia64/kvm/kvm-ia64.c
index d5f4e91..17fd65c 100644
--- a/arch/ia64/kvm/kvm-ia64.c
+++ b/arch/ia64/kvm/kvm-ia64.c
@@ -1824,6 +1824,9 @@ static int kvm_ia64_sync_dirty_log(struct kvm *kvm,
base = memslot->base_gfn / BITS_PER_LONG;
for (i = 0; i < n/sizeof(long); ++i) {
+ if (dirty_bitmap[base + i])
+ memslot->is_dirty = true;
+
memslot->dirty_bitmap[i] = dirty_bitmap[base + i];
dirty_bitmap[base + i] = 0;
}
@@ -1838,7 +1841,6 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
int r;
unsigned long n;
struct kvm_memory_slot *memslot;
- int is_dirty = 0;
mutex_lock(&kvm->slots_lock);
spin_lock(&kvm->arch.dirty_log_lock);
@@ -1847,16 +1849,17 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
if (r)
goto out;
- r = kvm_get_dirty_log(kvm, log, &is_dirty);
+ r = kvm_get_dirty_log(kvm, log);
if (r)
goto out;
+ memslot = &kvm->memslots->memslots[log->slot];
/* If nothing is dirty, don't bother messing with page tables. */
- if (is_dirty) {
+ if (memslot->is_dirty) {
kvm_flush_remote_tlbs(kvm);
- memslot = &kvm->memslots->memslots[log->slot];
n = kvm_dirty_bitmap_bytes(memslot);
memset(memslot->dirty_bitmap, 0, n);
+ memslot->is_dirty = false;
}
r = 0;
out:
diff --git a/arch/powerpc/kvm/book3s.c b/arch/powerpc/kvm/book3s.c
index 28e785f..4b074f1 100644
--- a/arch/powerpc/kvm/book3s.c
+++ b/arch/powerpc/kvm/book3s.c
@@ -1191,20 +1191,18 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
struct kvm_memory_slot *memslot;
struct kvm_vcpu *vcpu;
ulong ga, ga_end;
- int is_dirty = 0;
int r;
unsigned long n;
mutex_lock(&kvm->slots_lock);
- r = kvm_get_dirty_log(kvm, log, &is_dirty);
+ r = kvm_get_dirty_log(kvm, log);
if (r)
goto out;
+ memslot = &kvm->memslots->memslots[log->slot];
/* If nothing is dirty, don't bother messing with page tables. */
- if (is_dirty) {
- memslot = &kvm->memslots->memslots[log->slot];
-
+ if (memslot->is_dirty) {
ga = memslot->base_gfn << PAGE_SHIFT;
ga_end = ga + (memslot->npages << PAGE_SHIFT);
@@ -1213,6 +1211,7 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
n = kvm_dirty_bitmap_bytes(memslot);
memset(memslot->dirty_bitmap, 0, n);
+ memslot->is_dirty = false;
}
r = 0;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index b95a211..023c7f8 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2740,10 +2740,9 @@ static int kvm_vm_ioctl_reinject(struct kvm *kvm,
int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
struct kvm_dirty_log *log)
{
- int r, i;
+ int r;
struct kvm_memory_slot *memslot;
unsigned long n;
- unsigned long is_dirty = 0;
mutex_lock(&kvm->slots_lock);
@@ -2758,11 +2757,8 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
n = kvm_dirty_bitmap_bytes(memslot);
- for (i = 0; !is_dirty && i < n/sizeof(long); i++)
- is_dirty = memslot->dirty_bitmap[i];
-
/* If nothing is dirty, don't bother messing with page tables. */
- if (is_dirty) {
+ if (memslot->is_dirty) {
struct kvm_memslots *slots, *old_slots;
unsigned long *dirty_bitmap;
@@ -2784,6 +2780,7 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
}
memcpy(slots, kvm->memslots, sizeof(struct kvm_memslots));
slots->memslots[log->slot].dirty_bitmap = dirty_bitmap;
+ slots->memslots[log->slot].is_dirty = false;
old_slots = kvm->memslots;
rcu_assign_pointer(kvm->memslots, slots);
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index ce027d5..0aa6ecb 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -117,6 +117,7 @@ struct kvm_memory_slot {
unsigned long flags;
unsigned long *rmap;
unsigned long *dirty_bitmap;
+ bool is_dirty;
struct {
unsigned long rmap_pde;
int write_count;
@@ -335,8 +336,7 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
int kvm_dev_ioctl_check_extension(long ext);
-int kvm_get_dirty_log(struct kvm *kvm,
- struct kvm_dirty_log *log, int *is_dirty);
+int kvm_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log);
int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
struct kvm_dirty_log *log);
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 9ab1a77..7ab6312 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -768,13 +768,11 @@ int kvm_vm_ioctl_set_memory_region(struct kvm *kvm,
return kvm_set_memory_region(kvm, mem, user_alloc);
}
-int kvm_get_dirty_log(struct kvm *kvm,
- struct kvm_dirty_log *log, int *is_dirty)
+int kvm_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
{
struct kvm_memory_slot *memslot;
- int r, i;
+ int r;
unsigned long n;
- unsigned long any = 0;
r = -EINVAL;
if (log->slot >= KVM_MEMORY_SLOTS)
@@ -787,16 +785,10 @@ int kvm_get_dirty_log(struct kvm *kvm,
n = kvm_dirty_bitmap_bytes(memslot);
- for (i = 0; !any && i < n/sizeof(long); ++i)
- any = memslot->dirty_bitmap[i];
-
r = -EFAULT;
if (copy_to_user(log->dirty_bitmap, memslot->dirty_bitmap, n))
goto out;
- if (any)
- *is_dirty = 1;
-
r = 0;
out:
return r;
@@ -1193,6 +1185,7 @@ void mark_page_dirty(struct kvm *kvm, gfn_t gfn)
unsigned long rel_gfn = gfn - memslot->base_gfn;
generic___set_le_bit(rel_gfn, memslot->dirty_bitmap);
+ memslot->is_dirty = true;
}
}
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 1/12 applied today] KVM: x86: avoid unnecessary bitmap allocation when memslot is clean
From: Takuya Yoshikawa @ 2010-05-04 12:58 UTC (permalink / raw)
To: Takuya Yoshikawa
Cc: linux-arch, x86, arnd, kvm, kvm-ia64, fernando, mtosatti, agraf,
kvm-ppc, linux-kernel, yoshikawa.takuya, linuxppc-dev, mingo,
paulus, avi, hpa, tglx
In-Reply-To: <20100504215645.6448af8f.takuya.yoshikawa@gmail.com>
Although we always allocate a new dirty bitmap in x86's get_dirty_log(),
it is only used as a zero-source of copy_to_user() and freed right after
that when memslot is clean. This patch uses clear_user() instead of doing
this unnecessary zero-source allocation.
Performance improvement: as we can expect easily, the time needed to
allocate a bitmap is completely reduced. Furthermore, we can avoid the
tlb flush triggered by vmalloc() and get some good effects. In my test,
the improved ioctl was about 4 to 10 times faster than the original one
for clean slots.
Signed-off-by: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
---
arch/x86/kvm/x86.c | 37 +++++++++++++++++++++++--------------
1 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 6b2ce1d..b95a211 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2744,7 +2744,6 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
struct kvm_memory_slot *memslot;
unsigned long n;
unsigned long is_dirty = 0;
- unsigned long *dirty_bitmap = NULL;
mutex_lock(&kvm->slots_lock);
@@ -2759,27 +2758,30 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
n = kvm_dirty_bitmap_bytes(memslot);
- r = -ENOMEM;
- dirty_bitmap = vmalloc(n);
- if (!dirty_bitmap)
- goto out;
- memset(dirty_bitmap, 0, n);
-
for (i = 0; !is_dirty && i < n/sizeof(long); i++)
is_dirty = memslot->dirty_bitmap[i];
/* If nothing is dirty, don't bother messing with page tables. */
if (is_dirty) {
struct kvm_memslots *slots, *old_slots;
+ unsigned long *dirty_bitmap;
spin_lock(&kvm->mmu_lock);
kvm_mmu_slot_remove_write_access(kvm, log->slot);
spin_unlock(&kvm->mmu_lock);
- slots = kzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
- if (!slots)
- goto out_free;
+ r = -ENOMEM;
+ dirty_bitmap = vmalloc(n);
+ if (!dirty_bitmap)
+ goto out;
+ memset(dirty_bitmap, 0, n);
+ r = -ENOMEM;
+ slots = kzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
+ if (!slots) {
+ vfree(dirty_bitmap);
+ goto out;
+ }
memcpy(slots, kvm->memslots, sizeof(struct kvm_memslots));
slots->memslots[log->slot].dirty_bitmap = dirty_bitmap;
@@ -2788,13 +2790,20 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
synchronize_srcu_expedited(&kvm->srcu);
dirty_bitmap = old_slots->memslots[log->slot].dirty_bitmap;
kfree(old_slots);
+
+ r = -EFAULT;
+ if (copy_to_user(log->dirty_bitmap, dirty_bitmap, n)) {
+ vfree(dirty_bitmap);
+ goto out;
+ }
+ vfree(dirty_bitmap);
+ } else {
+ r = -EFAULT;
+ if (clear_user(log->dirty_bitmap, n))
+ goto out;
}
r = 0;
- if (copy_to_user(log->dirty_bitmap, dirty_bitmap, n))
- r = -EFAULT;
-out_free:
- vfree(dirty_bitmap);
out:
mutex_unlock(&kvm->slots_lock);
return r;
--
1.7.0.4
^ permalink raw reply related
* [RFC][PATCH 0/12] KVM, x86, ppc, asm-generic: moving dirty bitmaps to user space
From: Takuya Yoshikawa @ 2010-05-04 12:56 UTC (permalink / raw)
To: avi, mtosatti, agraf
Cc: linux-arch, arnd, kvm, kvm-ia64, fernando, x86, linux-kernel,
kvm-ppc, yoshikawa.takuya, linuxppc-dev, mingo, paulus, hpa, tglx
Hi, sorry for sending from my personal account.
The following series are all from me:
From: Takuya Yoshikawa <yoshikawa.takuya@oss.ntt.co.jp>
The 3rd version of "moving dirty bitmaps to user space".
>From this version, we add x86 and ppc and asm-generic people to CC lists.
[To KVM people]
Sorry for being late to reply your comments.
Avi,
- I've wrote an answer to your question in patch 5/12: drivers/vhost/vhost.c .
- I've considered to change the set_bit_user_non_atomic to an inline function,
but did not change because the other helpers in the uaccess.h are written as
macros. Anyway, I hope that x86 people will give us appropriate suggestions
about this.
- I thought that documenting about making bitmaps 64-bit aligned will be
written when we add an API to register user-allocated bitmaps. So probably
in the next series.
Avi, Alex,
- Could you check the ia64 and ppc parts, please? I tried to keep the logical
changes as small as possible.
I personally tried to build these with cross compilers. For ia64, I could check
build success with my patch series. But book3s, even without my patch series,
it failed with the following errors:
arch/powerpc/kvm/book3s_paired_singles.c: In function 'kvmppc_emulate_paired_single':
arch/powerpc/kvm/book3s_paired_singles.c:1289: error: the frame size of 2288 bytes is larger than 2048 bytes
make[1]: *** [arch/powerpc/kvm/book3s_paired_singles.o] Error 1
make: *** [arch/powerpc/kvm] Error 2
About changelog: there are two main changes from the 2nd version:
1. I changed the treatment of clean slots (see patch 1/12).
This was already applied today, thanks!
2. I changed the switch API. (see patch 11/12).
To show this API's advantage, I also did a test (see the end of this mail).
[To x86 people]
Hi, Thomas, Ingo, Peter,
Please review the patches 4,5/12. Because this is the first experience for
me to send patches to x86, please tell me if this lacks anything.
[To ppc people]
Hi, Benjamin, Paul, Alex,
Please see the patches 6,7/12. I first say sorry for that I've not tested these
yet. In that sense, these may not be in the quality for precise reviews. But I
will be happy if you would give me any comments.
Alex, could you help me? Though I have a plan to get PPC box in the future,
currently I cannot test these.
[To asm-generic people]
Hi, Arnd,
Please review the patch 8/12. This kind of macro is acceptable?
[Performance test]
We measured the tsc needed to the ioctl()s for getting dirty logs in
kernel.
Test environment
AMD Phenom(tm) 9850 Quad-Core Processor with 8GB memory
1. GUI test (running Ubuntu guest in graphical mode)
sudo qemu-system-x86_64 -hda dirtylog_test.img -boot c -m 4192 -net ...
We show a relatively stable part to compare how much time is needed
for the basic parts of dirty log ioctl.
get.org get.opt switch.opt
slots[7].len=32768 278379 66398 64024
slots[8].len=32768 181246 270 160
slots[7].len=32768 263961 64673 64494
slots[8].len=32768 181655 265 160
slots[7].len=32768 263736 64701 64610
slots[8].len=32768 182785 267 160
slots[7].len=32768 260925 65360 65042
slots[8].len=32768 182579 264 160
slots[7].len=32768 267823 65915 65682
slots[8].len=32768 186350 271 160
At a glance, we know our optimization improved significantly compared
to the original get dirty log ioctl. This is true for both get.opt and
switch.opt. This has a really big impact for the personal KVM users who
drive KVM in GUI mode on their usual PCs.
Next, we notice that switch.opt improved a hundred nano seconds or so for
these slots. Although this may sound a bit tiny improvement, we can feel
this as a difference of GUI's responses like mouse reactions.
To feel the difference, please try GUI on your PC with our patch series!
2. Live-migration test (4GB guest, write loop with 1GB buf)
We also did a live-migration test.
get.org get.opt switch.opt
slots[0].len=655360 797383 261144 222181
slots[1].len=3757047808 2186721 1965244 1842824
slots[2].len=637534208 1433562 1012723 1031213
slots[3].len=131072 216858 331 331
slots[4].len=131072 121635 225 164
slots[5].len=131072 120863 356 164
slots[6].len=16777216 121746 1133 156
slots[7].len=32768 120415 230 278
slots[8].len=32768 120368 216 149
slots[0].len=655360 806497 194710 223582
slots[1].len=3757047808 2142922 1878025 1895369
slots[2].len=637534208 1386512 1021309 1000345
slots[3].len=131072 221118 459 296
slots[4].len=131072 121516 272 166
slots[5].len=131072 122652 244 173
slots[6].len=16777216 123226 99185 149
slots[7].len=32768 121803 457 505
slots[8].len=32768 121586 216 155
slots[0].len=655360 766113 211317 213179
slots[1].len=3757047808 2155662 1974790 1842361
slots[2].len=637534208 1481411 1020004 1031352
slots[3].len=131072 223100 351 295
slots[4].len=131072 122982 436 164
slots[5].len=131072 122100 300 503
slots[6].len=16777216 123653 779 151
slots[7].len=32768 122617 284 157
slots[8].len=32768 122737 253 149
For slots other than 0,1,2 we can see the similar improvement.
Considering the fact that switch.opt does not depend on the bitmap length
except for kvm_mmu_slot_remove_write_access(), this is the cause of some
usec to msec time consumption: there might be some context switches.
But note that this was done with the workload which dirtied the memory
endlessly during the live-migration.
In usual workload, the number of dirty pages varies a lot for each iteration
and we should gain really a lot for relatively clean cases.
^ permalink raw reply
* Re: [PATCH] Fix DEBUG_PAGEALLOC on 603/e300
From: Benjamin Herrenschmidt @ 2010-05-04 5:13 UTC (permalink / raw)
To: Xianghua Xiao; +Cc: Maindoor, linuxppc-dev
In-Reply-To: <g2pe6bf505b1005030719r3194c665i61d0c5906e1d1254@mail.gmail.com>
On Mon, 2010-05-03 at 09:19 -0500, Xianghua Xiao wrote:
> Ok here is the test result I did this morning, in short with/without
> Ben's patch it will oops if you write to a freed page.
Probably lucky enough that the free_pages() is pushing the stuff out of
the TLB. At least my patch prevents writing to a non-existing hash table
though, thus avoiding memory corruption.
Cheers,
Ben.
^ permalink raw reply
* [PATCHv2 2/2] genirq: update doc
From: Guillaume Knispel @ 2010-05-04 0:23 UTC (permalink / raw)
To: linux-kernel, Linuxppc-dev
Cc: Randy Dunlap, Lars-Peter Clausen, Russell King,
Bartlomiej Zolnierkiewicz, Haavard Skinnemoen, Guillaume Knispel,
Peter Zijlstra, Ingo Molnar, Linus Torvalds, Thomas Gleixner
In-Reply-To: <20100504021941.ff7579c1.gknispel@proformatique.com>
Following "genirq: always build resend_irqs" and previous changes, this
commit updates the genirq DocBook.
Signed-off-by: Guillaume Knispel <gknispel@proformatique.com>
CC: linux-kernel@vger.kernel.org
CC: Linuxppc-dev@lists.ozlabs.org
CC: Bartlomiej Zolnierkiewicz <bzolnier@gmail.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Haavard Skinnemoen <hskinnemoen@atmel.com>
CC: Ingo Molnar <mingo@elte.hu>
CC: Lars-Peter Clausen <lars@metafoo.de>
CC: Linus Torvalds <torvalds@linux-foundation.org>
CC: Randy Dunlap <randy.dunlap@oracle.com>
CC: Peter Zijlstra <peterz@infradead.org>
CC: Russell King <linux@arm.linux.org.uk>
CC: Thomas Gleixner <tglx@linutronix.de>
---
Documentation/DocBook/genericirq.tmpl | 73 ++++++++++++++++++--------------
1 files changed, 41 insertions(+), 32 deletions(-)
diff --git a/Documentation/DocBook/genericirq.tmpl b/Documentation/DocBook/genericirq.tmpl
index 1448b33..7df5274 100644
--- a/Documentation/DocBook/genericirq.tmpl
+++ b/Documentation/DocBook/genericirq.tmpl
@@ -240,8 +240,6 @@ default_enable(irq)
default_disable(irq)
{
- if (!delay_disable(irq))
- desc->chip->mask(irq);
}
default_ack(irq)
@@ -264,7 +262,11 @@ noop(irq)
}
</programlisting>
- </para>
+ Note: default_disable() is empty so that an edge-triggered
+ interrupt happening between a disable_irq() and an enable_irq()
+ is caught by the kernel for later replay. See
+ <xref linkend="Delayed_interrupt_disable"/> for details.
+ </para>
</sect3>
</sect2>
<sect2 id="Default_flow_handler_implementations">
@@ -278,9 +280,14 @@ noop(irq)
<para>
The following control flow is implemented (simplified excerpt):
<programlisting>
-desc->chip->start();
+desc->chip->mask_ack();
+if (desc->status & inprogress)
+ return;
+desc->status |= inprogress;
handle_IRQ_event(desc->action);
-desc->chip->end();
+desc->status &= inprogress;
+if (!(desc->status & disabled))
+ desc->chip->unmask_ack();
</programlisting>
</para>
</sect3>
@@ -293,21 +300,23 @@ desc->chip->end();
<para>
The following control flow is implemented (simplified excerpt):
<programlisting>
-if (desc->status & running) {
- desc->chip->hold();
+if (desc->status & (inprogress | disabled)) {
desc->status |= pending | masked;
+ desc->chip->mask_ack();
return;
}
-desc->chip->start();
-desc->status |= running;
+desc->chip->ack();
+desc->status |= inprogress;
do {
- if (desc->status & masked)
- desc->chip->enable();
+ if ((desc->status & (pending | masked | disabled))
+ == (pending | masked)) {
+ desc->chip->unmask();
+ desc->status &= ~masked;
+ }
desc->status &= ~pending;
handle_IRQ_event(desc->action);
-} while (status & pending);
-desc->status &= ~running;
-desc->chip->end();
+} while ((status & (pending | disabled)) == pending);
+desc->status &= inprogress;
</programlisting>
</para>
</sect3>
@@ -342,9 +351,9 @@ handle_IRQ_event(desc->action);
<para>
The following control flow is implemented (simplified excerpt):
<programlisting>
-desc->chip->start();
+desc->chip->ack();
handle_IRQ_event(desc->action);
-desc->chip->end();
+desc->chip->eoi();
</programlisting>
</para>
</sect3>
@@ -361,22 +370,20 @@ desc->chip->end();
<sect2 id="Delayed_interrupt_disable">
<title>Delayed interrupt disable</title>
<para>
- This per interrupt selectable feature, which was introduced by Russell
- King in the ARM interrupt implementation, does not mask an interrupt
- at the hardware level when disable_irq() is called. The interrupt is
- kept enabled and is masked in the flow handler when an interrupt event
- happens. This prevents losing edge interrupts on hardware which does
- not store an edge interrupt event while the interrupt is disabled at
- the hardware level. When an interrupt arrives while the IRQ_DISABLED
- flag is set, then the interrupt is masked at the hardware level and
- the IRQ_PENDING bit is set. When the interrupt is re-enabled by
- enable_irq() the pending bit is checked and if it is set, the
- interrupt is resent either via hardware or by a software resend
- mechanism. (It's necessary to enable CONFIG_HARDIRQS_SW_RESEND when
- you want to use the delayed interrupt disable feature and your
- hardware is not capable of retriggering an interrupt.)
- The delayed interrupt disable can be runtime enabled, per interrupt,
- by setting the IRQ_DELAYED_DISABLE flag in the irq_desc status field.
+ At least when using default_disable as the ->disable() callback of a
+ chip handler, an interrupt is not masked at the hardware level when
+ disable_irq() is called. The interrupt is kept enabled and is masked
+ in the flow handler when an interrupt event happens. This prevents
+ losing edge interrupts on hardware which does not store an edge
+ interrupt event while the interrupt is disabled at the hardware level.
+ This makes use of the replay mechanism that is mandatory anyway to
+ prevent potential race conditions between an interrupt event and a call
+ to disable_irq() at the same time. When an interrupt arrives in
+ handle_edge_irq() while the IRQ_DISABLED flag is set, then the
+ interrupt is masked at the hardware level and the IRQ_PENDING bit is
+ set. When the interrupt is re-enabled by enable_irq() the pending bit
+ is checked and if it is set, the interrupt is resent either via
+ hardware or by a software resend mechanism.
</para>
</sect2>
</sect1>
@@ -387,6 +394,8 @@ desc->chip->end();
contains all the direct chip relevant functions, which
can be utilized by the irq flow implementations.
<itemizedlist>
+ <listitem><para>disable() - Optional</para></listitem>
+ <listitem><para>enable() - Optional</para></listitem>
<listitem><para>ack()</para></listitem>
<listitem><para>mask_ack() - Optional, recommended for performance</para></listitem>
<listitem><para>mask()</para></listitem>
--
1.6.2
^ permalink raw reply related
* [PATCHv2 1/2] genirq: reliably replay pending edge-triggered irq
From: Guillaume Knispel @ 2010-05-04 0:21 UTC (permalink / raw)
To: linux-kernel, Linuxppc-dev
Cc: Randy Dunlap, Lars-Peter Clausen, Russell King,
Bartlomiej Zolnierkiewicz, Haavard Skinnemoen, Guillaume Knispel,
Peter Zijlstra, Ingo Molnar, Linus Torvalds, Thomas Gleixner
In-Reply-To: <20100504021941.ff7579c1.gknispel@proformatique.com>
The following can happen:
CPU 1 CPU 2
disable_irq(): handle_edge_irq():
LOCK desc->lock (irqsave)
desc->status |= IRQ_DISABLED;
desc->chip->disable(irq);/*1*/
UNLOCK desc->lock (irqrestore)
LOCK desc->lock
desc->status |= (IRQ_PENDING
| IRQ_MASKED);
mask_ack_irq(desc, irq);
UNLOCK desc->lock
NOTE /*1*/: ->disable can point to default_disable().
Since commit:
76d2160147f43f982dfe881404cfde9fd0a9da21
genirq: do not mask interrupts by default
the delayed interrupt disable mechanism has been activated for every
user of default_disable() -- which used to mask the interrupt at
controller level before and is now a noop. The sequence describing a
race above will now indeed happen if an interrupt event occurs at any
time between the effective disable_irq() and the next effective
enable_irq().
Also note that even if ->disable does a masking, a similar race
can indeed happen even on a monoprocessor system if an interrupt event
occurs before just before the masking.
In order to avoid interrupt loss, an IRQ_PENDING interrupt must be
replayed when enable_irq() is called (or immediately after).
This replay (implemented in kernel/irq/resend.c) used to be reliable
only if:
* the interrupt controller driver implements a reliable retrigger()
callback
or
* CONFIG_HARDIRQS_SW_RESEND is defined (in this case the flow handler
can be executed in a tasklet running resend_irqs() )
So CONFIG_HARDIRQS_SW_RESEND was meant to be set on plateforms where it
exists a risk that edge interrupts are used on an interrupt controller
that does not support hard retrigger (or at least not reliably).
But CONFIG_HARDIRQS_SW_RESEND was only defined on arm and avr32
architectures, and other architectures exist which can have controllers
without a reliable retrigger(). Some examples:
* arch/powerpc/sysdev/cpm2.c arch/powerpc/sysdev/ipic.c
* arch/blackfin/mach-common/ints-priority.c
* arch/mips/alchemy/common/irq.c
* ...
With the present change, resend_irqs() is unconditionally built, so
that edge-triggered interrupts can not be lost.
The CONFIG_HARDIRQS_SW_RESEND option is not used anymore.
See http://lkml.org/lkml/2010/4/19/129 for the first discussion about
this problem.
Signed-off-by: Guillaume Knispel <gknispel@proformatique.com>
CC: linux-kernel@vger.kernel.org
CC: Linuxppc-dev@lists.ozlabs.org
CC: Bartlomiej Zolnierkiewicz <bzolnier@gmail.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Haavard Skinnemoen <hskinnemoen@atmel.com>
CC: Ingo Molnar <mingo@elte.hu>
CC: Lars-Peter Clausen <lars@metafoo.de>
CC: Linus Torvalds <torvalds@linux-foundation.org>
CC: Peter Zijlstra <peterz@infradead.org>
CC: Randy Dunlap <randy.dunlap@oracle.com>
CC: Russell King <linux@arm.linux.org.uk>
CC: Thomas Gleixner <tglx@linutronix.de>
---
kernel/irq/resend.c | 6 ------
1 files changed, 0 insertions(+), 6 deletions(-)
diff --git a/kernel/irq/resend.c b/kernel/irq/resend.c
index 090c376..3b20ce1 100644
--- a/kernel/irq/resend.c
+++ b/kernel/irq/resend.c
@@ -20,8 +20,6 @@
#include "internals.h"
-#ifdef CONFIG_HARDIRQS_SW_RESEND
-
/* Bitmap to handle software resend of interrupts: */
static DECLARE_BITMAP(irqs_resend, NR_IRQS);
@@ -46,8 +44,6 @@ static void resend_irqs(unsigned long arg)
/* Tasklet to handle resend: */
static DECLARE_TASKLET(resend_tasklet, resend_irqs, 0);
-#endif
-
/*
* IRQ resend
*
@@ -71,11 +67,9 @@ void check_irq_resend(struct irq_desc *desc, unsigned int irq)
desc->status = (status & ~IRQ_PENDING) | IRQ_REPLAY;
if (!desc->chip->retrigger || !desc->chip->retrigger(irq)) {
-#ifdef CONFIG_HARDIRQS_SW_RESEND
/* Set it pending and activate the softirq: */
set_bit(irq, irqs_resend);
tasklet_schedule(&resend_tasklet);
-#endif
}
}
}
--
1.6.2
^ permalink raw reply related
* [PATCHv2 0/2] genirq: reliably replay pending edge-triggered irq (plus doc)
From: Guillaume Knispel @ 2010-05-04 0:19 UTC (permalink / raw)
To: linux-kernel, Linuxppc-dev
Cc: Randy Dunlap, Lars-Peter Clausen, Russell King,
Bartlomiej Zolnierkiewicz, Haavard Skinnemoen, Guillaume Knispel,
Peter Zijlstra, Ingo Molnar, Linus Torvalds, Thomas Gleixner
In the following series:
[1/2] implements the proposal I made at the end of the thread
http://lkml.org/lkml/2010/4/19/129 to reliably support replay
of edge-triggered interrupts on all architectures when using
disable_irq() / enable_irq().
Proper replays of pending edge-triggered interrupts was
depending on CONFIG_HARDIRQS_SW_RESEND which only seems to have
been noticed for plateforms of ARM and AVR32 architecture while
it should also have been used on other architectures to get the
correct behavior. So the patch unconditionally builds the
resend_irqs() tasklet and its scheduling
(CONFIG_HARDIRQS_SW_RESEND is not used anymore).
I only tested an equivalent patch for linux-2.6.22.18 on powerpc
for a board with an MPC8555E (using a portC line on the CPM2 PIC),
and build-tested this one for x86.
[2/2] updates Documentation/DocBook/genericirq.tmpl, taking
into account 1/2 other previous undocumented changes to genirq.
History:
[v2] - Don't touch archs Kconfig and defconfigs
(CONFIG_HARDIRQS_SW_RESEND is left unused)
- Don't make unrelated change to a comment in
arch/x86/kernel/apic/io_apic.c
- (hopefully) Better commit message for [1/2]
- Correct spelling errors in [2/2]
[v1] - initial patch
Guillaume Knispel (2):
genirq: reliably replay pending edge-triggered irq
genirq: update doc
Documentation/DocBook/genericirq.tmpl | 73 ++++++++++++++++++--------------
kernel/irq/resend.c | 6 ---
2 files changed, 41 insertions(+), 38 deletions(-)
^ permalink raw reply
* [PATCH 2/2] [v2] powerpc/alsa: update MPC8610 HPCD audio drivers to support multiple components
From: Timur Tabi @ 2010-05-03 23:01 UTC (permalink / raw)
To: broonie, kumar.gala, alsa-devel, linuxppc-dev
Update the Freescale MPC8610 HPCD audio drivers to support the new multiple-
component architecture in ASoC.
Signed-off-by: Timur Tabi <timur@freescale.com>
---
sound/soc/fsl/fsl_dma.c | 299 ++++++-----
sound/soc/fsl/fsl_dma.h | 19 +-
sound/soc/fsl/fsl_ssi.c | 211 +++++----
sound/soc/fsl/fsl_ssi.h | 25 -
sound/soc/fsl/mpc8610_hpcd.c | 1174 ++++++++++++++++++++----------------------
5 files changed, 844 insertions(+), 884 deletions(-)
rewrite sound/soc/fsl/mpc8610_hpcd.c (62%)
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
index 857e9f7..ff0064c 100644
--- a/sound/soc/fsl/fsl_dma.c
+++ b/sound/soc/fsl/fsl_dma.c
@@ -3,10 +3,11 @@
*
* Author: Timur Tabi <timur@freescale.com>
*
- * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
- * under the terms of the GNU General Public License version 2. This
- * program is licensed "as is" without any warranty of any kind, whether
- * express or implied.
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
*
* This driver implements ASoC support for the Elo DMA controller, which is
* the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
@@ -15,10 +16,11 @@
#include <linux/module.h>
#include <linux/init.h>
-#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
+#include <linux/of_platform.h>
+#include <linux/list.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -51,26 +53,16 @@
#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
SNDRV_PCM_RATE_CONTINUOUS)
-/* DMA global data. This structure is used by fsl_dma_open() to determine
- * which DMA channels to assign to a substream. Unfortunately, ASoC V1 does
- * not allow the machine driver to provide this information to the PCM
- * driver in advance, and there's no way to differentiate between the two
- * DMA controllers. So for now, this driver only supports one SSI device
- * using two DMA channels. We cannot support multiple DMA devices.
- *
- * ssi_stx_phys: bus address of SSI STX register
- * ssi_srx_phys: bus address of SSI SRX register
- * dma_channel: pointer to the DMA channel's registers
- * irq: IRQ for this DMA channel
- * assigned: set to 1 if that DMA channel is assigned to a substream
- */
-static struct {
+struct dma_object {
+ struct list_head list;
+ struct snd_soc_platform_driver dai;
dma_addr_t ssi_stx_phys;
dma_addr_t ssi_srx_phys;
- struct ccsr_dma_channel __iomem *dma_channel[2];
- unsigned int irq[2];
- unsigned int assigned[2];
-} dma_global_data;
+ struct ccsr_dma_channel __iomem *channel;
+ unsigned int irq;
+ bool assigned;
+ char path[1];
+};
/*
* The number of DMA links to use. Two is the bare minimum, but if you
@@ -87,8 +79,6 @@ static struct {
* structure.
*
* @link[]: array of link descriptors
- * @controller_id: which DMA controller (0, 1, ...)
- * @channel_id: which DMA channel on the controller (0, 1, 2, ...)
* @dma_channel: pointer to the DMA channel's registers
* @irq: IRQ for this DMA channel
* @substream: pointer to the substream object, needed by the ISR
@@ -103,8 +93,6 @@ static struct {
*/
struct fsl_dma_private {
struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
- unsigned int controller_id;
- unsigned int channel_id;
struct ccsr_dma_channel __iomem *dma_channel;
unsigned int irq;
struct snd_pcm_substream *substream;
@@ -211,6 +199,9 @@ static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
{
struct fsl_dma_private *dma_private = dev_id;
+ struct snd_pcm_substream *substream = dma_private->substream;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
irqreturn_t ret = IRQ_NONE;
u32 sr, sr2 = 0;
@@ -221,11 +212,8 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
sr = in_be32(&dma_channel->sr);
if (sr & CCSR_DMA_SR_TE) {
- dev_err(dma_private->substream->pcm->card->dev,
- "DMA transmit error (controller=%u channel=%u irq=%u\n",
- dma_private->controller_id,
- dma_private->channel_id, irq);
- fsl_dma_abort_stream(dma_private->substream);
+ dev_err(dev, "dma transmit error\n");
+ fsl_dma_abort_stream(substream);
sr2 |= CCSR_DMA_SR_TE;
ret = IRQ_HANDLED;
}
@@ -234,11 +222,8 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
ret = IRQ_HANDLED;
if (sr & CCSR_DMA_SR_PE) {
- dev_err(dma_private->substream->pcm->card->dev,
- "DMA%u programming error (channel=%u irq=%u)\n",
- dma_private->controller_id,
- dma_private->channel_id, irq);
- fsl_dma_abort_stream(dma_private->substream);
+ dev_err(dev, "dma programming error\n");
+ fsl_dma_abort_stream(substream);
sr2 |= CCSR_DMA_SR_PE;
ret = IRQ_HANDLED;
}
@@ -252,8 +237,6 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
ret = IRQ_HANDLED;
if (sr & CCSR_DMA_SR_EOSI) {
- struct snd_pcm_substream *substream = dma_private->substream;
-
/* Tell ALSA we completed a period. */
snd_pcm_period_elapsed(substream);
@@ -304,10 +287,8 @@ static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
fsl_dma_hardware.buffer_bytes_max,
&pcm->streams[0].substream->dma_buffer);
if (ret) {
- dev_err(card->dev,
- "Can't allocate playback DMA buffer (size=%u)\n",
- fsl_dma_hardware.buffer_bytes_max);
- return -ENOMEM;
+ dev_err(card->dev, "can't allocate playback dma buffer\n");
+ return ret;
}
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
@@ -315,10 +296,8 @@ static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
&pcm->streams[1].substream->dma_buffer);
if (ret) {
snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
- dev_err(card->dev,
- "Can't allocate capture DMA buffer (size=%u)\n",
- fsl_dma_hardware.buffer_bytes_max);
- return -ENOMEM;
+ dev_err(card->dev, "can't allocate capture dma buffer\n");
+ return ret;
}
return 0;
@@ -389,6 +368,10 @@ static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
static int fsl_dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct dma_object *dma =
+ container_of(rtd->platform->driver, struct dma_object, dai);
struct fsl_dma_private *dma_private;
struct ccsr_dma_channel __iomem *dma_channel;
dma_addr_t ld_buf_phys;
@@ -406,52 +389,44 @@ static int fsl_dma_open(struct snd_pcm_substream *substream)
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
- dev_err(substream->pcm->card->dev, "invalid buffer size\n");
+ dev_err(dev, "invalid buffer size\n");
return ret;
}
channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
- if (dma_global_data.assigned[channel]) {
- dev_err(substream->pcm->card->dev,
- "DMA channel already assigned\n");
+ if (dma->assigned) {
+ dev_err(dev, "dma channel already assigned\n");
return -EBUSY;
}
- dma_private = dma_alloc_coherent(substream->pcm->card->dev,
- sizeof(struct fsl_dma_private), &ld_buf_phys, GFP_KERNEL);
+ dma_private = dma_alloc_coherent(dev, sizeof(struct fsl_dma_private),
+ &ld_buf_phys, GFP_KERNEL);
if (!dma_private) {
- dev_err(substream->pcm->card->dev,
- "can't allocate DMA private data\n");
+ dev_err(dev, "can't allocate dma private data\n");
return -ENOMEM;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- dma_private->ssi_sxx_phys = dma_global_data.ssi_stx_phys;
+ dma_private->ssi_sxx_phys = dma->ssi_stx_phys;
else
- dma_private->ssi_sxx_phys = dma_global_data.ssi_srx_phys;
+ dma_private->ssi_sxx_phys = dma->ssi_srx_phys;
- dma_private->dma_channel = dma_global_data.dma_channel[channel];
- dma_private->irq = dma_global_data.irq[channel];
+ dma_private->dma_channel = dma->channel;
+ dma_private->irq = dma->irq;
dma_private->substream = substream;
dma_private->ld_buf_phys = ld_buf_phys;
dma_private->dma_buf_phys = substream->dma_buffer.addr;
- /* We only support one DMA controller for now */
- dma_private->controller_id = 0;
- dma_private->channel_id = channel;
-
ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "DMA", dma_private);
if (ret) {
- dev_err(substream->pcm->card->dev,
- "can't register ISR for IRQ %u (ret=%i)\n",
+ dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
dma_private->irq, ret);
- dma_free_coherent(substream->pcm->card->dev,
- sizeof(struct fsl_dma_private),
+ dma_free_coherent(dev, sizeof(struct fsl_dma_private),
dma_private, dma_private->ld_buf_phys);
return ret;
}
- dma_global_data.assigned[channel] = 1;
+ dma->assigned = 1;
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
@@ -545,6 +520,8 @@ static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_dma_private *dma_private = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
/* Number of bits per sample */
unsigned int sample_size =
@@ -605,8 +582,7 @@ static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
break;
default:
/* We should never get here */
- dev_err(substream->pcm->card->dev,
- "unsupported sample size %u\n", sample_size);
+ dev_err(dev, "unsupported sample size %u\n", sample_size);
return -EINVAL;
}
@@ -688,6 +664,8 @@ static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_dma_private *dma_private = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
dma_addr_t position;
snd_pcm_uframes_t frames;
@@ -709,8 +687,7 @@ static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
if ((position < dma_private->dma_buf_phys) ||
(position > dma_private->dma_buf_end)) {
- dev_err(substream->pcm->card->dev,
- "dma pointer is out of range, halting stream\n");
+ dev_err(dev, "dma pointer is out of range, halting stream\n");
return SNDRV_PCM_POS_XRUN;
}
@@ -771,26 +748,28 @@ static int fsl_dma_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_dma_private *dma_private = runtime->private_data;
- int dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct dma_object *dma =
+ container_of(rtd->platform->driver, struct dma_object, dai);
if (dma_private) {
if (dma_private->irq)
free_irq(dma_private->irq, dma_private);
if (dma_private->ld_buf_phys) {
- dma_unmap_single(substream->pcm->card->dev,
- dma_private->ld_buf_phys,
- sizeof(dma_private->link), DMA_TO_DEVICE);
+ dma_unmap_single(dev, dma_private->ld_buf_phys,
+ sizeof(dma_private->link),
+ DMA_TO_DEVICE);
}
/* Deallocate the fsl_dma_private structure */
- dma_free_coherent(substream->pcm->card->dev,
- sizeof(struct fsl_dma_private),
- dma_private, dma_private->ld_buf_phys);
+ dma_free_coherent(dev, sizeof(struct fsl_dma_private),
+ dma_private, dma_private->ld_buf_phys);
substream->runtime->private_data = NULL;
}
- dma_global_data.assigned[dir] = 0;
+ dma->assigned = 0;
return 0;
}
@@ -813,6 +792,41 @@ static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
}
}
+/* List of DMA nodes that we've probed */
+static LIST_HEAD(dma_list);
+
+/**
+ * fsl_soc_dma_to_dai - returning the DAI struct for a given DMA node
+ *
+ * When a machine driver registers itself with ASoC, it must provide the
+ * address of the DAI structures that it wants to connect. Most drivers simply
+ * reference global data structures in the various drivers, but we PowerPC
+ * developers are better than that. This driver keeps a list of every DMA
+ * node that it probes, and when the machine driver wants to reference a
+ * DMA DAI, this function provides that service.
+ *
+ * Hopefully one day, ASoC will just want the name of the DMA device, not
+ * some obscure pointer, and this function will go away.
+ */
+struct snd_soc_platform_driver *fsl_soc_dma_to_dai(const char *path,
+ dma_addr_t ssi_stx_phys, dma_addr_t ssi_srx_phys)
+{
+ struct list_head *ptr;
+ struct dma_object *dma;
+
+ list_for_each(ptr, &dma_list) {
+ dma = list_entry(ptr, struct dma_object, list);
+ if (strcmp(path, dma->path) == 0) {
+ dma->ssi_stx_phys = ssi_stx_phys;
+ dma->ssi_srx_phys = ssi_srx_phys;
+ return &dma->dai;
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL(fsl_soc_dma_to_dai);
+
static struct snd_pcm_ops fsl_dma_ops = {
.open = fsl_dma_open,
.close = fsl_dma_close,
@@ -822,80 +836,91 @@ static struct snd_pcm_ops fsl_dma_ops = {
.pointer = fsl_dma_pointer,
};
-struct snd_soc_platform_driver fsl_soc_platform = {
- .name = "fsl-dma",
- .ops = &fsl_dma_ops,
- .pcm_new = fsl_dma_new,
- .pcm_free = fsl_dma_free_dma_buffers,
-};
-EXPORT_SYMBOL_GPL(fsl_soc_platform);
-
-/**
- * fsl_dma_configure: store the DMA parameters from the fabric driver.
- *
- * This function is called by the ASoC fabric driver to give us the DMA and
- * SSI channel information.
- *
- * Unfortunately, ASoC V1 does make it possible to determine the DMA/SSI
- * data when a substream is created, so for now we need to store this data
- * into a global variable. This means that we can only support one DMA
- * controller, and hence only one SSI.
- */
-int fsl_dma_configure(struct fsl_dma_info *dma_info)
+static int __devinit fsl_soc_dma_probe(struct of_device *of_dev,
+ const struct of_device_id *match)
{
- static int initialized;
+ struct dma_object *dma;
+ struct device_node *np = of_dev->node;
+ int ret;
- /* We only support one DMA controller for now */
- if (initialized)
- return 0;
+ dma = kzalloc(sizeof(*dma) + strlen(np->full_name), GFP_KERNEL);
+ if (!dma) {
+ dev_err(&of_dev->dev, "could not allocate dma object\n");
+ return -ENOMEM;
+ }
- dma_global_data.ssi_stx_phys = dma_info->ssi_stx_phys;
- dma_global_data.ssi_srx_phys = dma_info->ssi_srx_phys;
- dma_global_data.dma_channel[0] = dma_info->dma_channel[0];
- dma_global_data.dma_channel[1] = dma_info->dma_channel[1];
- dma_global_data.irq[0] = dma_info->dma_irq[0];
- dma_global_data.irq[1] = dma_info->dma_irq[1];
- dma_global_data.assigned[0] = 0;
- dma_global_data.assigned[1] = 0;
-
- initialized = 1;
- return 1;
-}
-EXPORT_SYMBOL_GPL(fsl_dma_configure);
+ strcpy(dma->path, np->full_name);
+ dma->dai.name = dma->path;
+ dma->dai.ops = &fsl_dma_ops;
+ dma->dai.pcm_new = fsl_dma_new;
+ dma->dai.pcm_free = fsl_dma_free_dma_buffers;
-static int __devinit fsl_soc_platform_probe(struct platform_device *pdev)
-{
- return snd_soc_register_platform(&pdev->dev, -1, &fsl_soc_platform);
+ ret = snd_soc_register_platform(&of_dev->dev, 0, &dma->dai);
+ if (ret) {
+ dev_err(&of_dev->dev, "could not register platform\n");
+ kfree(dma);
+ return ret;
+ }
+
+ dma->channel = of_iomap(np, 0);
+ dma->irq = irq_of_parse_and_map(np, 0);
+ list_add(&dma->list, &dma_list);
+
+ return 0;
}
-static int __devexit fsl_soc_platform_remove(struct platform_device *pdev)
+static int __devexit fsl_soc_dma_remove(struct of_device *of_dev)
{
- snd_soc_unregister_platform(&pdev->dev, -1);
+ struct list_head *n, *ptr;
+ struct dma_object *dma;
+
+ list_for_each_safe(ptr, n, &dma_list) {
+ dma = list_entry(ptr, struct dma_object, list);
+ list_del_init(ptr);
+
+ snd_soc_unregister_platform(&of_dev->dev, 0);
+ iounmap(dma->channel);
+ irq_dispose_mapping(dma->irq);
+ kfree(dma);
+ }
+
return 0;
}
-static struct platform_driver fsl_pcm_driver = {
- .driver = {
- .name = "fsl-pcm-audio",
- .owner = THIS_MODULE,
- },
- .probe = fsl_soc_platform_probe,
- .remove = __devexit_p(fsl_soc_platform_remove),
+static const struct of_device_id fsl_soc_dma_ids[] = {
+ { .compatible = "fsl,ssi-dma-channel", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_soc_dma_ids);
+
+static struct of_platform_driver fsl_soc_dma_driver = {
+ .name = "fsl-soc-dma-dai",
+ .match_table = fsl_soc_dma_ids,
+ .probe = fsl_soc_dma_probe,
+ .remove = __devexit_p(fsl_soc_dma_remove),
};
-static int __init snd_fsl_pcm_init(void)
+static int __init fsl_soc_dma_init(void)
{
- return platform_driver_register(&fsl_pcm_driver);
+ pr_info("Freescale Elo DMA ASoC PCM Driver\n");
+
+ return of_register_platform_driver(&fsl_soc_dma_driver);
}
-module_init(snd_fsl_pcm_init);
-static void __exit snd_fsl_pcm_exit(void)
+static void __exit fsl_soc_dma_exit(void)
{
- platform_driver_unregister(&fsl_pcm_driver);
+ of_unregister_platform_driver(&fsl_soc_dma_driver);
}
-module_exit(snd_fsl_pcm_exit);
+
+/* We want the DMA driver to be initialized before the SSI driver, so that
+ * when the SSI driver calls fsl_soc_dma_dai_from_node(), the DMA driver
+ * will already have been probed. The easiest way to do that is to make the
+ * __init function called via arch_initcall().
+ */
+arch_initcall(fsl_soc_dma_init);
+module_exit(fsl_soc_dma_exit);
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
-MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM module");
-MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h
index 253bd3c..88c686d 100644
--- a/sound/soc/fsl/fsl_dma.h
+++ b/sound/soc/fsl/fsl_dma.h
@@ -126,24 +126,9 @@ struct fsl_dma_link_descriptor {
u8 res[4]; /* Reserved */
} __attribute__ ((aligned(32), packed));
-/* DMA information needed to create a snd_soc_dai object
- *
- * ssi_stx_phys: bus address of SSI STX register to use
- * ssi_srx_phys: bus address of SSI SRX register to use
- * dma[0]: points to the DMA channel to use for playback
- * dma[1]: points to the DMA channel to use for capture
- * dma_irq[0]: IRQ of the DMA channel to use for playback
- * dma_irq[1]: IRQ of the DMA channel to use for capture
- */
-struct fsl_dma_info {
- dma_addr_t ssi_stx_phys;
- dma_addr_t ssi_srx_phys;
- struct ccsr_dma_channel __iomem *dma_channel[2];
- unsigned int dma_irq[2];
-};
-
extern struct snd_soc_platform_driver fsl_soc_platform;
-int fsl_dma_configure(struct fsl_dma_info *dma_info);
+struct snd_soc_platform_driver *fsl_soc_dma_to_dai(const char *path,
+ dma_addr_t ssi_stx_phys, dma_addr_t ssi_srx_phys);
#endif
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
index 780e243..56e66c2 100644
--- a/sound/soc/fsl/fsl_ssi.c
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -3,10 +3,11 @@
*
* Author: Timur Tabi <timur@freescale.com>
*
- * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
- * under the terms of the GNU General Public License version 2. This
- * program is licensed "as is" without any warranty of any kind, whether
- * express or implied.
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
*/
#include <linux/init.h>
@@ -14,6 +15,7 @@
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
+#include <linux/of_platform.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -70,33 +72,31 @@
/**
* fsl_ssi_private: per-SSI private data
*
- * @name: short name for this device ("SSI0", "SSI1", etc)
* @ssi: pointer to the SSI's registers
* @ssi_phys: physical address of the SSI registers
* @irq: IRQ of this SSI
* @first_stream: pointer to the stream that was opened first
* @second_stream: pointer to second stream
- * @dev: struct device pointer
* @playback: the number of playback streams opened
* @capture: the number of capture streams opened
* @asynchronous: 0=synchronous mode, 1=asynchronous mode
* @cpu_dai: the CPU DAI for this device
* @dev_attr: the sysfs device attribute structure
* @stats: SSI statistics
+ * @name: name for this device
*/
struct fsl_ssi_private {
- char name[8];
struct ccsr_ssi __iomem *ssi;
dma_addr_t ssi_phys;
unsigned int irq;
struct snd_pcm_substream *first_stream;
struct snd_pcm_substream *second_stream;
- struct device *dev;
unsigned int playback;
unsigned int capture;
int asynchronous;
struct snd_soc_dai_driver cpu_dai_drv;
struct device_attribute dev_attr;
+ struct platform_device *pdev;
struct {
unsigned int rfrc;
@@ -121,6 +121,8 @@ struct fsl_ssi_private {
unsigned int tfe1;
unsigned int tfe0;
} stats;
+
+ char name[1];
};
/**
@@ -289,8 +291,10 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
int ret;
+ /* The 'name' should not have any slashes in it. */
ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0,
- ssi_private->name, ssi_private);
+ strrchr(ssi_private->name, '/') + 1,
+ ssi_private);
if (ret < 0) {
dev_err(substream->pcm->card->dev,
"could not claim irq %u\n", ssi_private->irq);
@@ -522,56 +526,15 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
}
}
-/**
- * fsl_ssi_set_sysclk: set the clock frequency and direction
- *
- * This function is called by the machine driver to tell us what the clock
- * frequency and direction are.
- *
- * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
- * and we don't care about the frequency. Return an error if the direction
- * is not SND_SOC_CLOCK_IN.
- *
- * @clk_id: reserved, should be zero
- * @freq: the frequency of the given clock ID, currently ignored
- * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
- */
-static int fsl_ssi_set_sysclk(struct snd_soc_dai *cpu_dai,
- int clk_id, unsigned int freq, int dir)
-{
-
- return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
-}
-
-/**
- * fsl_ssi_set_fmt: set the serial format.
- *
- * This function is called by the machine driver to tell us what serial
- * format to use.
- *
- * Currently, we only support I2S mode. Return an error if the format is
- * not SND_SOC_DAIFMT_I2S.
- *
- * @format: one of SND_SOC_DAIFMT_xxx
- */
-static int fsl_ssi_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
-{
- return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
-}
-
-/**
- * fsl_ssi_dai_template: template CPU DAI for the SSI
- */
static struct snd_soc_dai_ops fsl_ssi_dai_ops = {
.startup = fsl_ssi_startup,
.hw_params = fsl_ssi_hw_params,
.shutdown = fsl_ssi_shutdown,
.trigger = fsl_ssi_trigger,
- .set_sysclk = fsl_ssi_set_sysclk,
- .set_fmt = fsl_ssi_set_fmt,
};
-struct snd_soc_dai_driver fsl_ssi_dai = {
+/* Template for the CPU dai driver structure */
+static struct snd_soc_dai_driver fsl_ssi_dai_template = {
.playback = {
/* The SSI does not support monaural audio. */
.channels_min = 2,
@@ -587,7 +550,6 @@ struct snd_soc_dai_driver fsl_ssi_dai = {
},
.ops = &fsl_ssi_dai_ops,
};
-EXPORT_SYMBOL_GPL(fsl_ssi_dai);
/* Show the statistics of a flag only if its interrupt is enabled. The
* compiler will optimze this code to a no-op if the interrupt is not
@@ -639,92 +601,163 @@ static ssize_t fsl_sysfs_ssi_show(struct device *dev,
return length;
}
-static int fsl_ssi_probe(struct platform_device *pdev)
+/**
+ * Make every character in a string lower-case
+ */
+static void make_lowercase(char *s)
+{
+ char *p = s;
+ char c;
+
+ while ((c = *p)) {
+ if ((c >= 'A') && (c <= 'Z'))
+ *p = c + ('a' - 'A');
+ p++;
+ }
+}
+
+static int __devinit fsl_ssi_probe(struct of_device *of_dev,
+ const struct of_device_id *match)
{
- struct fsl_ssi_info *ssi_info = dev_get_platdata(&pdev->dev);
struct fsl_ssi_private *ssi_private;
int ret = 0;
struct device_attribute *dev_attr;
+ struct device_node *np = of_dev->node;
+ const char *sprop;
+ struct resource res;
+ char name[64];
+ struct platform_device *pdev;
+ void *platform_data;
+
+ /* We are only interested in SSIs with a codec phandle in them, so let's
+ * make sure this SSI has one.
+ */
+ if (!of_get_property(np, "codec-handle", NULL))
+ return -ENODEV;
+
+ /* We only support the SSI in "I2S Slave" mode */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop || strcmp(sprop, "i2s-slave")) {
+ dev_notice(&of_dev->dev, "mode %s is unsupported\n", sprop);
+ return -ENODEV;
+ }
- ssi_private = kzalloc(sizeof(struct fsl_ssi_private), GFP_KERNEL);
+ ssi_private = kzalloc(sizeof(struct fsl_ssi_private) +
+ strlen(np->full_name), GFP_KERNEL);
if (!ssi_private) {
- dev_err(ssi_info->dev, "could not allocate DAI object\n");
+ dev_err(&of_dev->dev, "could not allocate DAI object\n");
return -ENOMEM;
}
- dev_attr = &ssi_private->dev_attr;
+ /* Initialize this copy of the CPU DAI driver structure */
+ memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template,
+ sizeof(fsl_ssi_dai_template));
+ ssi_private->cpu_dai_drv.name = ssi_private->name;
+
+ /* The name we register with ASoC is the full node name */
+ strlcpy(ssi_private->name, np->full_name, sizeof(ssi_private->name));
+
+ /* Get the addresses and IRQ */
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret) {
+ dev_err(&of_dev->dev, "could not determine device resources\n");
+ kfree(ssi_private);
+ return ret;
+ }
+ ssi_private->ssi = ioremap(res.start, 1 + res.end - res.start);
+ ssi_private->ssi_phys = res.start;
+ ssi_private->irq = irq_of_parse_and_map(np, 0);
- sprintf(ssi_private->name, "ssi%u", (u8) ssi_info->id);
- ssi_private->ssi = ssi_info->ssi;
- ssi_private->ssi_phys = ssi_info->ssi_phys;
- ssi_private->irq = ssi_info->irq;
- ssi_private->dev = ssi_info->dev;
- ssi_private->asynchronous = ssi_info->asynchronous;
+ /* Are the RX and the TX clocks locked? */
+ if (of_find_property(np, "fsl,ssi-asynchronous", NULL))
+ ssi_private->asynchronous = 1;
+ else
+ ssi_private->cpu_dai_drv.symmetric_rates = 1;
/* Initialize the the device_attribute structure */
- dev_attr->attr.name = "ssi-stats";
+ dev_attr = &ssi_private->dev_attr;
+ dev_attr->attr.name = "statistics";
dev_attr->attr.mode = S_IRUGO;
dev_attr->show = fsl_sysfs_ssi_show;
- ret = device_create_file(ssi_private->dev, dev_attr);
+ ret = device_create_file(&of_dev->dev, dev_attr);
if (ret) {
- dev_err(ssi_info->dev, "could not create sysfs %s file\n",
+ dev_err(&of_dev->dev, "could not create sysfs %s file\n",
ssi_private->dev_attr.attr.name);
kfree(ssi_private);
return ret;
}
- dev_set_drvdata(ssi_private->dev, ssi_private);
- fsl_ssi_dai.name = ssi_private->name;
- fsl_ssi_dai.symmetric_rates = 1;
+ /* Register with ASoC */
+ dev_set_drvdata(&of_dev->dev, ssi_private);
- ret = snd_soc_register_dai(ssi_private->dev, ssi_info->id, &fsl_ssi_dai);
+ ret = snd_soc_register_dai(&of_dev->dev, 0, &ssi_private->cpu_dai_drv);
if (ret != 0) {
- dev_err(ssi_info->dev, "failed to register DAI: %d\n", ret);
+ dev_err(&of_dev->dev, "failed to register DAI: %d\n", ret);
kfree(ssi_private);
return ret;
}
- return ret;
+ /* Trigger the machine driver's probe function. The platform driver
+ * name of the machine driver is taken from the /model node of the
+ * device tree. We also pass the address of the CPU DAI driver
+ * structure.
+ */
+ sprop = of_get_property(of_find_node_by_path("/"), "model", NULL);
+ snprintf(name, sizeof(name), "snd-soc-%s", sprop);
+ make_lowercase(name);
+
+ platform_data = &ssi_private->cpu_dai_drv;
+ pdev = platform_device_register_data(&of_dev->dev, name, -1,
+ &platform_data, sizeof(void *));
+
+ ssi_private->pdev = pdev;
+
+ return 0;
}
/**
* fsl_ssi_destroy_dai: destroy the snd_soc_dai object
*
- * This function undoes the operations of fsl_ssi_create_dai()
+ * This function undoes the operations of fsl_ssi_probe()
*/
-static int fsl_ssi_remove(struct platform_device *pdev)
+static int fsl_ssi_remove(struct of_device *of_dev)
{
- struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev);
- struct fsl_ssi_info *ssi_info = dev_get_platdata(&pdev->dev);
-
- device_remove_file(ssi_private->dev, &ssi_private->dev_attr);
+ struct fsl_ssi_private *ssi_private = dev_get_drvdata(&of_dev->dev);
- snd_soc_unregister_dai(&pdev->dev, ssi_info->id);
+ platform_device_unregister(ssi_private->pdev);
+ snd_soc_unregister_dai(&of_dev->dev, 0);
+ device_remove_file(&of_dev->dev, &ssi_private->dev_attr);
kfree(ssi_private);
+ dev_set_drvdata(&of_dev->dev, NULL);
+
return 0;
}
-static struct platform_driver fsl_ssi_driver = {
+static const struct of_device_id fsl_ssi_ids[] = {
+ { .compatible = "fsl,mpc8610-ssi", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_ssi_ids);
+
+static struct of_platform_driver fsl_ssi_driver = {
+ .name = "fsl-ssi-dai",
+ .match_table = fsl_ssi_ids,
.probe = fsl_ssi_probe,
- .remove = __devexit_p(fsl_ssi_remove),
- .driver = {
- .name = "fsl-ssi-dai",
- .owner = THIS_MODULE,
- },
+ .remove = fsl_ssi_remove,
};
static int __init fsl_ssi_init(void)
{
printk(KERN_INFO "Freescale Synchronous Serial Interface (SSI) ASoC Driver\n");
- return platform_driver_register(&fsl_ssi_driver);
+ return of_register_platform_driver(&fsl_ssi_driver);
}
static void __exit fsl_ssi_exit(void)
{
- platform_driver_unregister(&fsl_ssi_driver);
+ of_unregister_platform_driver(&fsl_ssi_driver);
}
module_init(fsl_ssi_init);
@@ -732,4 +765,4 @@ module_exit(fsl_ssi_exit);
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h
index 965fd86..2173000 100644
--- a/sound/soc/fsl/fsl_ssi.h
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -196,30 +196,5 @@ struct ccsr_ssi {
#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
#define CCSR_SSI_SOR_SYNRST 0x00000001
-/* Instantiation data for an SSI interface
- *
- * This structure contains all the information that the the SSI driver needs
- * to instantiate an SSI interface with ALSA. The machine driver should
- * create this structure, fill it in, call fsl_ssi_create_dai(), and then
- * delete the structure.
- *
- * id: which SSI this is (0, 1, etc. )
- * ssi: pointer to the SSI's registers
- * ssi_phys: physical address of the SSI registers
- * irq: IRQ of this SSI
- * dev: struct device, used to create the sysfs statistics file
- * asynchronous: 0=synchronous mode, 1=asynchronous mode
-*/
-struct fsl_ssi_info {
- unsigned int id;
- struct ccsr_ssi __iomem *ssi;
- dma_addr_t ssi_phys;
- unsigned int irq;
- struct device *dev;
- int asynchronous;
-};
-
-extern struct snd_soc_dai_driver fsl_ssi_dai;
-
#endif
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
dissimilarity index 62%
index ad8d763..3e422f7 100644
--- a/sound/soc/fsl/mpc8610_hpcd.c
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -1,616 +1,558 @@
-/**
- * Freescale MPC8610HPCD ALSA SoC Fabric driver
- *
- * Author: Timur Tabi <timur@freescale.com>
- *
- * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
- * under the terms of the GNU General Public License version 2. This
- * program is licensed "as is" without any warranty of any kind, whether
- * express or implied.
- */
-
-#include <linux/module.h>
-#include <linux/interrupt.h>
-#include <linux/of_device.h>
-#include <linux/of_platform.h>
-#include <sound/soc.h>
-#include <asm/immap_86xx.h>
-
-#include "../codecs/cs4270.h"
-#include "fsl_dma.h"
-#include "fsl_ssi.h"
-
-/**
- * mpc8610_hpcd_data: fabric-specific ASoC device data
- *
- * This structure contains data for a single sound platform device on an
- * MPC8610 HPCD. Some of the data is taken from the device tree.
- */
-struct mpc8610_hpcd_data {
- struct snd_soc_dai_link dai;
- struct snd_soc_card machine;
- unsigned int dai_format;
- unsigned int codec_clk_direction;
- unsigned int cpu_clk_direction;
- unsigned int clk_frequency;
- struct ccsr_guts __iomem *guts;
- struct ccsr_ssi __iomem *ssi;
- unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
- unsigned int ssi_irq;
- unsigned int dma_id; /* 0 = DMA1, 1 = DMA2, etc */
- unsigned int dma_irq[2];
- struct ccsr_dma_channel __iomem *dma[2];
- unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
-};
-
-/**
- * mpc8610_hpcd_machine_probe: initalize the board
- *
- * This function is called when platform_device_add() is called. It is used
- * to initialize the board-specific hardware.
- *
- * Here we program the DMACR and PMUXCR registers.
- */
-static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
-{
- struct mpc8610_hpcd_data *machine_data =
- sound_device->dev.platform_data;
-
- /* Program the signal routing between the SSI and the DMA */
- guts_set_dmacr(machine_data->guts, machine_data->dma_id,
- machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI);
- guts_set_dmacr(machine_data->guts, machine_data->dma_id,
- machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI);
-
- guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
- machine_data->dma_channel_id[0], 0);
- guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
- machine_data->dma_channel_id[1], 0);
-
- switch (machine_data->ssi_id) {
- case 0:
- clrsetbits_be32(&machine_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
- break;
- case 1:
- clrsetbits_be32(&machine_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
- break;
- }
-
- return 0;
-}
-
-/**
- * mpc8610_hpcd_startup: program the board with various hardware parameters
- *
- * This function takes board-specific information, like clock frequencies
- * and serial data formats, and passes that information to the codec and
- * transport drivers.
- */
-static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- struct mpc8610_hpcd_data *machine_data =
- rtd->card->dev->platform_data;
- int ret = 0;
-
- /* Tell the CPU driver what the serial protocol is. */
- ret = snd_soc_dai_set_fmt(cpu_dai, machine_data->dai_format);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set CPU driver audio format\n");
- return ret;
- }
-
- /* Tell the codec driver what the serial protocol is. */
- ret = snd_soc_dai_set_fmt(codec_dai, machine_data->dai_format);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set codec driver audio format\n");
- return ret;
- }
-
- /*
- * Tell the CPU driver what the clock frequency is, and whether it's a
- * slave or master.
- */
- ret = snd_soc_dai_set_sysclk(cpu_dai, 0,
- machine_data->clk_frequency,
- machine_data->cpu_clk_direction);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set CPU driver clock parameters\n");
- return ret;
- }
-
- /*
- * Tell the codec driver what the MCLK frequency is, and whether it's
- * a slave or master.
- */
- ret = snd_soc_dai_set_sysclk(codec_dai, 0,
- machine_data->clk_frequency,
- machine_data->codec_clk_direction);
- if (ret < 0) {
- dev_err(substream->pcm->card->dev,
- "could not set codec driver clock params\n");
- return ret;
- }
-
- return 0;
-}
-
-/**
- * mpc8610_hpcd_machine_remove: Remove the sound device
- *
- * This function is called to remove the sound device for one SSI. We
- * de-program the DMACR and PMUXCR register.
- */
-int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
-{
- struct mpc8610_hpcd_data *machine_data =
- sound_device->dev.platform_data;
-
- /* Restore the signal routing */
-
- guts_set_dmacr(machine_data->guts, machine_data->dma_id,
- machine_data->dma_channel_id[0], 0);
- guts_set_dmacr(machine_data->guts, machine_data->dma_id,
- machine_data->dma_channel_id[1], 0);
-
- switch (machine_data->ssi_id) {
- case 0:
- clrsetbits_be32(&machine_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
- break;
- case 1:
- clrsetbits_be32(&machine_data->guts->pmuxcr,
- CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
- break;
- }
-
- return 0;
-}
-
-/**
- * mpc8610_hpcd_ops: ASoC fabric driver operations
- */
-static struct snd_soc_ops mpc8610_hpcd_ops = {
- .startup = mpc8610_hpcd_startup,
-};
-
-/**
- * mpc8610_hpcd_probe: OF probe function for the fabric driver
- *
- * This function gets called when an SSI node is found in the device tree.
- *
- * Although this is a fabric driver, the SSI node is the "master" node with
- * respect to audio hardware connections. Therefore, we create a new ASoC
- * device for each new SSI node that has a codec attached.
- *
- * FIXME: Currently, we only support one DMA controller, so if there are
- * multiple SSI nodes with codecs, only the first will be supported.
- *
- * FIXME: Even if we did support multiple DMA controllers, we have no
- * mechanism for assigning DMA controllers and channels to the individual
- * SSI devices. We also probably aren't compatible with the generic Elo DMA
- * device driver.
- */
-static int mpc8610_hpcd_probe(struct of_device *ofdev,
- const struct of_device_id *match)
-{
- struct device_node *np = ofdev->node;
- struct device_node *codec_np = NULL;
- struct device_node *guts_np = NULL;
- struct device_node *dma_np = NULL;
- struct device_node *dma_channel_np = NULL;
- const phandle *codec_ph;
- const char *sprop;
- const u32 *iprop;
- struct resource res;
- struct platform_device *sound_device = NULL;
- struct mpc8610_hpcd_data *machine_data;
- struct fsl_ssi_info ssi_info;
- struct fsl_dma_info dma_info;
- int ret = -ENODEV;
- unsigned int playback_dma_channel;
- unsigned int capture_dma_channel;
-
- machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
- if (!machine_data)
- return -ENOMEM;
-
- memset(&ssi_info, 0, sizeof(ssi_info));
- memset(&dma_info, 0, sizeof(dma_info));
-
- ssi_info.dev = &ofdev->dev;
-
- /*
- * We are only interested in SSIs with a codec phandle in them, so let's
- * make sure this SSI has one.
- */
- codec_ph = of_get_property(np, "codec-handle", NULL);
- if (!codec_ph)
- goto error;
-
- codec_np = of_find_node_by_phandle(*codec_ph);
- if (!codec_np)
- goto error;
-
- /* The MPC8610 HPCD only knows about the CS4270 codec, so reject
- anything else. */
- if (!of_device_is_compatible(codec_np, "cirrus,cs4270"))
- goto error;
-
- /* Get the device ID */
- iprop = of_get_property(np, "cell-index", NULL);
- if (!iprop) {
- dev_err(&ofdev->dev, "cell-index property not found\n");
- ret = -EINVAL;
- goto error;
- }
- machine_data->ssi_id = *iprop;
- ssi_info.id = *iprop;
-
- /* Get the serial format and clock direction. */
- sprop = of_get_property(np, "fsl,mode", NULL);
- if (!sprop) {
- dev_err(&ofdev->dev, "fsl,mode property not found\n");
- ret = -EINVAL;
- goto error;
- }
-
- if (strcasecmp(sprop, "i2s-slave") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_I2S;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
-
- /*
- * In i2s-slave mode, the codec has its own clock source, so we
- * need to get the frequency from the device tree and pass it to
- * the codec driver.
- */
- iprop = of_get_property(codec_np, "clock-frequency", NULL);
- if (!iprop || !*iprop) {
- dev_err(&ofdev->dev, "codec bus-frequency property "
- "is missing or invalid\n");
- ret = -EINVAL;
- goto error;
- }
- machine_data->clk_frequency = *iprop;
- } else if (strcasecmp(sprop, "i2s-master") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_I2S;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else if (strcasecmp(sprop, "lj-slave") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
- } else if (strcasecmp(sprop, "lj-master") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else if (strcasecmp(sprop, "rj-slave") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
- } else if (strcasecmp(sprop, "rj-master") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else if (strcasecmp(sprop, "ac97-slave") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_AC97;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
- } else if (strcasecmp(sprop, "ac97-master") == 0) {
- machine_data->dai_format = SND_SOC_DAIFMT_AC97;
- machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
- machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
- } else {
- dev_err(&ofdev->dev,
- "unrecognized fsl,mode property \"%s\"\n", sprop);
- ret = -EINVAL;
- goto error;
- }
-
- if (!machine_data->clk_frequency) {
- dev_err(&ofdev->dev, "unknown clock frequency\n");
- ret = -EINVAL;
- goto error;
- }
-
- /* Read the SSI information from the device tree */
- ret = of_address_to_resource(np, 0, &res);
- if (ret) {
- dev_err(&ofdev->dev, "could not obtain SSI address\n");
- goto error;
- }
- if (!res.start) {
- dev_err(&ofdev->dev, "invalid SSI address\n");
- goto error;
- }
- ssi_info.ssi_phys = res.start;
-
- machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi));
- if (!machine_data->ssi) {
- dev_err(&ofdev->dev, "could not map SSI address %x\n",
- ssi_info.ssi_phys);
- ret = -EINVAL;
- goto error;
- }
- ssi_info.ssi = machine_data->ssi;
-
-
- /* Get the IRQ of the SSI */
- machine_data->ssi_irq = irq_of_parse_and_map(np, 0);
- if (!machine_data->ssi_irq) {
- dev_err(&ofdev->dev, "could not get SSI IRQ\n");
- ret = -EINVAL;
- goto error;
- }
- ssi_info.irq = machine_data->ssi_irq;
-
- /* Do we want to use asynchronous mode? */
- ssi_info.asynchronous =
- of_find_property(np, "fsl,ssi-asynchronous", NULL) ? 1 : 0;
- if (ssi_info.asynchronous)
- dev_info(&ofdev->dev, "using asynchronous mode\n");
-
- /* Map the global utilities registers. */
- guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
- if (!guts_np) {
- dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
- ret = -EINVAL;
- goto error;
- }
- machine_data->guts = of_iomap(guts_np, 0);
- of_node_put(guts_np);
- if (!machine_data->guts) {
- dev_err(&ofdev->dev, "could not map GUTS\n");
- ret = -EINVAL;
- goto error;
- }
-
- /* Find the DMA channels to use. Both SSIs need to use the same DMA
- * controller, so let's use DMA#1.
- */
- for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") {
- iprop = of_get_property(dma_np, "cell-index", NULL);
- if (iprop && (*iprop == 0)) {
- of_node_put(dma_np);
- break;
- }
- }
- if (!dma_np) {
- dev_err(&ofdev->dev, "could not find DMA node\n");
- ret = -EINVAL;
- goto error;
- }
- machine_data->dma_id = *iprop;
-
- /* SSI1 needs to use DMA Channels 0 and 1, and SSI2 needs to use DMA
- * channels 2 and 3. This is just how the MPC8610 is wired
- * internally.
- */
- playback_dma_channel = (machine_data->ssi_id == 0) ? 0 : 2;
- capture_dma_channel = (machine_data->ssi_id == 0) ? 1 : 3;
-
- /*
- * Find the DMA channels to use.
- */
- while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) {
- iprop = of_get_property(dma_channel_np, "cell-index", NULL);
- if (iprop && (*iprop == playback_dma_channel)) {
- /* dma_channel[0] and dma_irq[0] are for playback */
- dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0);
- dma_info.dma_irq[0] =
- irq_of_parse_and_map(dma_channel_np, 0);
- machine_data->dma_channel_id[0] = *iprop;
- continue;
- }
- if (iprop && (*iprop == capture_dma_channel)) {
- /* dma_channel[1] and dma_irq[1] are for capture */
- dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0);
- dma_info.dma_irq[1] =
- irq_of_parse_and_map(dma_channel_np, 0);
- machine_data->dma_channel_id[1] = *iprop;
- continue;
- }
- }
- if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] ||
- !dma_info.dma_irq[0] || !dma_info.dma_irq[1]) {
- dev_err(&ofdev->dev, "could not find DMA channels\n");
- ret = -EINVAL;
- goto error;
- }
-
- dma_info.ssi_stx_phys = ssi_info.ssi_phys +
- offsetof(struct ccsr_ssi, stx0);
- dma_info.ssi_srx_phys = ssi_info.ssi_phys +
- offsetof(struct ccsr_ssi, srx0);
-
- /* We have the DMA information, so tell the DMA driver what it is */
- if (!fsl_dma_configure(&dma_info)) {
- dev_err(&ofdev->dev, "could not instantiate DMA device\n");
- ret = -EBUSY;
- goto error;
- }
-
- /*
- * Initialize our DAI data structure. We should probably get this
- * information from the device tree.
- */
- machine_data->dai.name = "CS4270";
- machine_data->dai.stream_name = "CS4270";
-
- iprop = of_get_property(codec_np, "reg", NULL);
- if (!iprop) {
- dev_err(&ofdev->dev, "codec node is missing 'reg' property\n");
- goto error;
- }
- machine_data->dai.codec_id = *iprop;
-
- machine_data->dai.cpu_dai_drv = &fsl_ssi_dai;
- machine_data->dai.codec_dai_drv = &cs4270_dai; /* The codec_dai we want */
- machine_data->dai.codec_drv = &soc_codec_device_cs4270;
- machine_data->dai.ops = &mpc8610_hpcd_ops;
- machine_data->dai.platform_drv = &fsl_soc_platform;
-
- machine_data->machine.probe = mpc8610_hpcd_machine_probe;
- machine_data->machine.remove = mpc8610_hpcd_machine_remove;
- machine_data->machine.name = "MPC8610 HPCD";
- machine_data->machine.num_links = 1;
- machine_data->machine.dai_link = &machine_data->dai;
-
- /* Allocate a new audio platform device structure */
- sound_device = platform_device_alloc("soc-audio", -1);
- if (!sound_device) {
- dev_err(&ofdev->dev, "platform device allocation failed\n");
- ret = -ENOMEM;
- goto error;
- }
-
- /* Set the platform device and ASoC device to point to each other */
- platform_set_drvdata(sound_device, &machine_data->machine);
-
- /* Tell ASoC to probe us. This will call mpc8610_hpcd_machine.probe(),
- if it exists. */
- ret = platform_device_add(sound_device);
-
- if (ret) {
- dev_err(&ofdev->dev, "platform device add failed\n");
- goto error;
- }
-
- dev_set_drvdata(&ofdev->dev, sound_device);
-
- return 0;
-
-error:
- of_node_put(codec_np);
- of_node_put(guts_np);
- of_node_put(dma_np);
- of_node_put(dma_channel_np);
-
- if (sound_device)
- platform_device_unregister(sound_device);
-
- if (ssi_info.ssi)
- iounmap(ssi_info.ssi);
-
- if (ssi_info.irq)
- irq_dispose_mapping(ssi_info.irq);
-
- if (dma_info.dma_channel[0])
- iounmap(dma_info.dma_channel[0]);
-
- if (dma_info.dma_channel[1])
- iounmap(dma_info.dma_channel[1]);
-
- if (dma_info.dma_irq[0])
- irq_dispose_mapping(dma_info.dma_irq[0]);
-
- if (dma_info.dma_irq[1])
- irq_dispose_mapping(dma_info.dma_irq[1]);
-
- if (machine_data->guts)
- iounmap(machine_data->guts);
-
- kfree(machine_data);
-
- return ret;
-}
-
-/**
- * mpc8610_hpcd_remove: remove the OF device
- *
- * This function is called when the OF device is removed.
- */
-static int mpc8610_hpcd_remove(struct of_device *ofdev)
-{
- struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev);
- struct mpc8610_hpcd_data *machine_data =
- sound_device->dev.platform_data;
-
- platform_device_unregister(sound_device);
-
- if (machine_data->ssi)
- iounmap(machine_data->ssi);
-
- if (machine_data->dma[0])
- iounmap(machine_data->dma[0]);
-
- if (machine_data->dma[1])
- iounmap(machine_data->dma[1]);
-
- if (machine_data->dma_irq[0])
- irq_dispose_mapping(machine_data->dma_irq[0]);
-
- if (machine_data->dma_irq[1])
- irq_dispose_mapping(machine_data->dma_irq[1]);
-
- if (machine_data->guts)
- iounmap(machine_data->guts);
-
- kfree(machine_data);
- sound_device->dev.platform_data = NULL;
-
- dev_set_drvdata(&ofdev->dev, NULL);
-
- return 0;
-}
-
-static struct of_device_id mpc8610_hpcd_match[] = {
- {
- .compatible = "fsl,mpc8610-ssi",
- },
- {}
-};
-MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match);
-
-static struct of_platform_driver mpc8610_hpcd_of_driver = {
- .owner = THIS_MODULE,
- .name = "mpc8610_hpcd",
- .match_table = mpc8610_hpcd_match,
- .probe = mpc8610_hpcd_probe,
- .remove = mpc8610_hpcd_remove,
-};
-
-/**
- * mpc8610_hpcd_init: fabric driver initialization.
- *
- * This function is called when this module is loaded.
- */
-static int __init mpc8610_hpcd_init(void)
-{
- int ret;
-
- printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n");
-
- ret = of_register_platform_driver(&mpc8610_hpcd_of_driver);
-
- if (ret)
- printk(KERN_ERR
- "mpc8610-hpcd: failed to register platform driver\n");
-
- return ret;
-}
-
-/**
- * mpc8610_hpcd_exit: fabric driver exit
- *
- * This function is called when this driver is unloaded.
- */
-static void __exit mpc8610_hpcd_exit(void)
-{
- of_unregister_platform_driver(&mpc8610_hpcd_of_driver);
-}
-
-module_init(mpc8610_hpcd_init);
-module_exit(mpc8610_hpcd_exit);
-
-MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
-MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver");
-MODULE_LICENSE("GPL");
+/**
+ * Freescale MPC8610HPCD ALSA SoC Machine driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <sound/soc.h>
+#include <asm/immap_86xx.h>
+
+#include "../codecs/cs4270.h"
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/* There's only one global utilities register */
+static phys_addr_t guts_phys;
+
+/**
+ * mpc8610_hpcd_data: machine-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * MPC8610 HPCD. Some of the data is taken from the device tree.
+ */
+struct mpc8610_hpcd_data {
+ struct snd_soc_dai_link dai[2];
+ struct snd_soc_card card;
+ unsigned int dai_format;
+ unsigned int codec_clk_direction;
+ unsigned int cpu_clk_direction;
+ unsigned int clk_frequency;
+ unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
+ unsigned int dma_id[0]; /* 0 = DMA1, 1 = DMA2, etc */
+ unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+ char dai_name[32];
+};
+
+/**
+ * mpc8610_hpcd_machine_probe: initialize the board
+ *
+ * This function is used to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
+{
+ struct snd_soc_card *card = platform_get_drvdata(sound_device);
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(card, struct mpc8610_hpcd_data, card);
+ struct ccsr_guts __iomem *guts;
+
+ guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
+ if (!guts) {
+ dev_err(card->dev, "could not map global utilities\n");
+ return -ENOMEM;
+ }
+
+ /* Program the signal routing between the SSI and the DMA */
+ guts_set_dmacr(guts, machine_data->dma_id[0],
+ machine_data->dma_channel_id[0],
+ CCSR_GUTS_DMACR_DEV_SSI);
+ guts_set_dmacr(guts, machine_data->dma_id[1],
+ machine_data->dma_channel_id[1],
+ CCSR_GUTS_DMACR_DEV_SSI);
+
+ guts_set_pmuxcr_dma(guts, machine_data->dma_id[0],
+ machine_data->dma_channel_id[0], 0);
+ guts_set_pmuxcr_dma(guts, machine_data->dma_id[1],
+ machine_data->dma_channel_id[1], 0);
+
+ switch (machine_data->ssi_id) {
+ case 0:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
+ break;
+ case 1:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
+ break;
+ }
+
+ iounmap(guts);
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(rtd->card, struct mpc8610_hpcd_data, card);
+ struct device *dev = rtd->card->dev;
+ int ret = 0;
+
+ /* Tell the codec driver what the serial protocol is. */
+ ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format);
+ if (ret < 0) {
+ dev_err(dev, "could not set codec driver audio format\n");
+ return ret;
+ }
+
+ /*
+ * Tell the codec driver what the MCLK frequency is, and whether it's
+ * a slave or master.
+ */
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
+ machine_data->clk_frequency,
+ machine_data->codec_clk_direction);
+ if (ret < 0) {
+ dev_err(dev, "could not set codec driver clock params\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI. We
+ * de-program the DMACR and PMUXCR register.
+ */
+static int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
+{
+ struct snd_soc_card *card = platform_get_drvdata(sound_device);
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(card, struct mpc8610_hpcd_data, card);
+ struct ccsr_guts __iomem *guts;
+
+ guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
+ if (!guts) {
+ dev_err(card->dev, "could not map global utilities\n");
+ return -ENOMEM;
+ }
+
+ /* Restore the signal routing */
+
+ guts_set_dmacr(guts, machine_data->dma_id[0],
+ machine_data->dma_channel_id[0], 0);
+ guts_set_dmacr(guts, machine_data->dma_id[1],
+ machine_data->dma_channel_id[1], 0);
+
+ switch (machine_data->ssi_id) {
+ case 0:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+ break;
+ case 1:
+ clrsetbits_be32(&guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
+ break;
+ }
+
+ iounmap(guts);
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_ops: ASoC machine driver operations
+ */
+static struct snd_soc_ops mpc8610_hpcd_ops = {
+ .startup = mpc8610_hpcd_startup,
+};
+
+/**
+ * get_node_by_phandle_name - get a node by its phandle name
+ *
+ * This function takes a node, the name of a property in that node, and a
+ * compatible string. Assuming the property is a phandle to another node,
+ * it returns that node, (optionally) if that node is compatible.
+ *
+ * If the property is not a phandle, or the node it points to is not compatible
+ * with the specific string, then NULL is returned.
+ */
+static struct device_node *get_node_by_phandle_name(struct device_node *np,
+ const char *name,
+ const char *compatible)
+{
+ const phandle *ph;
+ int len;
+
+ ph = of_get_property(np, name, &len);
+ if (!ph || (len != sizeof(phandle)))
+ return NULL;
+
+ np = of_find_node_by_phandle(*ph);
+ if (!np)
+ return NULL;
+
+ if (compatible && !of_device_is_compatible(np, compatible)) {
+ of_node_put(np);
+ return NULL;
+ }
+
+ return np;
+}
+
+/**
+ * get_parent_cell_index -- return the cell-index of the parent of a node
+ *
+ * Return the value of the cell-index property of the parent of the given
+ * node. This is used for DMA channel nodes that need to know the DMA ID
+ * of the controller they are on.
+ */
+static int get_parent_cell_index(struct device_node *np)
+{
+ struct device_node *parent = of_get_parent(np);
+ const u32 *iprop;
+
+ if (!parent)
+ return -1;
+
+ iprop = of_get_property(parent, "cell-index", NULL);
+ of_node_put(parent);
+
+ if (!iprop)
+ return -1;
+
+ return *iprop;
+}
+
+static int get_dma_channel(struct device_node *ssi_np,
+ const char *compatible,
+ dma_addr_t ssi_stx_phys,
+ dma_addr_t ssi_srx_phys,
+ struct snd_soc_dai_link *dai,
+ unsigned int *dma_channel_id,
+ unsigned int *dma_id)
+{
+ struct device_node *dma_channel_np;
+ const u32 *iprop;
+
+ dma_channel_np = get_node_by_phandle_name(ssi_np, compatible,
+ "fsl,ssi-dma-channel");
+ if (!dma_channel_np)
+ return -EINVAL;
+
+ dai->platform_drv = fsl_soc_dma_to_dai(dma_channel_np->full_name,
+ ssi_stx_phys, ssi_srx_phys);
+
+ iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+ if (!iprop) {
+ of_node_put(dma_channel_np);
+ return -EINVAL;
+ }
+
+ *dma_channel_id = *iprop;
+ *dma_id = get_parent_cell_index(dma_channel_np);
+ of_node_put(dma_channel_np);
+
+ return 0;
+}
+/**
+ * mpc8610_hpcd_probe: platform probe function for the machine driver
+ *
+ * Although this is a machine driver, the SSI node is the "master" node with
+ * respect to audio hardware connections. Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int mpc8610_hpcd_probe(struct platform_device *pdev)
+{
+ struct snd_soc_dai_driver **platform_data = pdev->dev.platform_data;
+ struct snd_soc_dai_driver *cpu_dai_drv = *platform_data;
+ struct device *dev = pdev->dev.parent;
+ struct of_device *of_dev = container_of(dev, struct of_device, dev);
+ struct device_node *np = of_dev->node;
+ struct device_node *codec_np = NULL;
+ struct device_node *dma_np = NULL;
+ struct device_node *dma_channel_np = NULL;
+ struct platform_device *sound_device = NULL;
+ struct mpc8610_hpcd_data *machine_data;
+ int id;
+ dma_addr_t ssi_stx_phys;
+ dma_addr_t ssi_srx_phys;
+ int ret = -ENODEV;
+ const char *sprop;
+ const u32 *iprop;
+ struct resource res;
+
+ /* We are only interested in SSIs with a codec phandle in them,
+ * so let's make sure this SSI has one. The MPC8610 HPCD only
+ * knows about the CS4270 codec, so reject anything else.
+ */
+ codec_np = get_node_by_phandle_name(np, "codec-handle",
+ "cirrus,cs4270");
+ if (!codec_np) {
+ dev_err(dev, "invalid codec node\n");
+ return -EINVAL;
+ }
+
+ machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
+ if (!machine_data)
+ return -ENOMEM;
+
+ machine_data->dai[0].cpu_dai_id = 0;
+ machine_data->dai[0].codec_dai_id = 0;
+ machine_data->dai[0].platform_id = 0;
+
+ machine_data->dai[0].name = machine_data->dai_name;
+
+ machine_data->dai[0].cpu_dai_drv = cpu_dai_drv;
+ machine_data->dai[0].codec_dai_drv = &cs4270_dai;
+ machine_data->dai[0].codec_drv = &soc_codec_device_cs4270;
+ machine_data->dai[0].ops = &mpc8610_hpcd_ops;
+
+ /* Store the codec name, it will be used as the codec DAI name */
+ of_modalias_node(codec_np, machine_data->dai_name,
+ sizeof(machine_data->dai_name));
+
+ memcpy(&machine_data->dai[1], &machine_data->dai[0],
+ sizeof(struct snd_soc_dai_link));
+
+ /* Get the device ID */
+ iprop = of_get_property(np, "cell-index", NULL);
+ if (!iprop) {
+ dev_err(&pdev->dev, "cell-index property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->ssi_id = *iprop;
+ id = *iprop;
+
+ /* Get the serial format and clock direction. */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop) {
+ dev_err(&pdev->dev, "fsl,mode property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (strcasecmp(sprop, "i2s-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+ /* In i2s-slave mode, the codec has its own clock source, so we
+ * need to get the frequency from the device tree and pass it to
+ * the codec driver.
+ */
+ iprop = of_get_property(codec_np, "clock-frequency", NULL);
+ if (!iprop || !*iprop) {
+ dev_err(&pdev->dev, "codec bus-frequency "
+ "property is missing or invalid\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->clk_frequency = *iprop;
+ } else if (strcasecmp(sprop, "i2s-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "lj-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "lj-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "rj-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "rj-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "ac97-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "ac97-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else {
+ dev_err(&pdev->dev,
+ "unrecognized fsl,mode property '%s'\n", sprop);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (!machine_data->clk_frequency) {
+ dev_err(&pdev->dev, "unknown clock frequency\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Read the SSI information from the device tree */
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret || !res.start) {
+ dev_err(&pdev->dev, "could not obtain SSI address\n");
+ goto error;
+ }
+ ssi_stx_phys = res.start + offsetof(struct ccsr_ssi, stx0);
+ ssi_srx_phys = res.start + offsetof(struct ccsr_ssi, srx0);
+
+ /* Find the playback DMA channel to use. */
+ ret = get_dma_channel(np, "fsl,playback-dma", ssi_stx_phys,
+ ssi_srx_phys, &machine_data->dai[0],
+ &machine_data->dma_channel_id[0],
+ &machine_data->dma_id[0]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
+ goto error;
+ }
+
+ /* Find the capture DMA channel to use. */
+ ret = get_dma_channel(np, "fsl,capture-dma", ssi_stx_phys,
+ ssi_srx_phys, &machine_data->dai[1],
+ &machine_data->dma_channel_id[1],
+ &machine_data->dma_id[1]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+ goto error;
+ }
+
+ /* Initialize our DAI data structure. */
+ iprop = of_get_property(codec_np, "reg", NULL);
+ if (!iprop) {
+ dev_err(&pdev->dev, "codec node is missing 'reg' property\n");
+ goto error;
+ }
+ machine_data->dai[0].codec_id = *iprop;
+ machine_data->dai[1].codec_id = *iprop;
+
+ machine_data->dai[0].stream_name = "playback";
+ machine_data->dai[1].stream_name = "capture";
+
+ machine_data->card.probe = mpc8610_hpcd_machine_probe;
+ machine_data->card.remove = mpc8610_hpcd_machine_remove;
+ machine_data->card.name = "MPC8610 HPCD";
+ machine_data->card.num_links = 2;
+ machine_data->card.dai_link = machine_data->dai;
+
+ /* Allocate a new audio platform device structure */
+ sound_device = platform_device_alloc("soc-audio", -1);
+ if (!sound_device) {
+ dev_err(&pdev->dev, "platform device alloc failed\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Associate the card data with the sound device */
+ platform_set_drvdata(sound_device, &machine_data->card);
+
+ /* Register with ASoC */
+ ret = platform_device_add(sound_device);
+ if (ret) {
+ dev_err(&pdev->dev, "platform device add failed\n");
+ goto error;
+ }
+
+ of_node_put(codec_np);
+
+ return 0;
+
+error:
+ of_node_put(codec_np);
+ of_node_put(dma_np);
+ of_node_put(dma_channel_np);
+
+ if (sound_device)
+ platform_device_unregister(sound_device);
+
+ kfree(machine_data);
+
+ return ret;
+}
+
+/**
+ * mpc8610_hpcd_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int __devexit mpc8610_hpcd_remove(struct platform_device *pdev)
+{
+ struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
+ struct snd_soc_card *card = platform_get_drvdata(sound_device);
+ struct mpc8610_hpcd_data *machine_data =
+ container_of(card, struct mpc8610_hpcd_data, card);
+
+ platform_device_unregister(sound_device);
+
+ kfree(machine_data);
+ sound_device->dev.platform_data = NULL;
+
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver mpc8610_hpcd_driver = {
+ .probe = mpc8610_hpcd_probe,
+ .remove = __devexit_p(mpc8610_hpcd_remove),
+ .driver = {
+ /* The name must match the 'model' property in the device tree,
+ * in lowercase letters.
+ */
+ .name = "snd-soc-mpc8610hpcd",
+ .owner = THIS_MODULE,
+ },
+};
+
+/**
+ * mpc8610_hpcd_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init mpc8610_hpcd_init(void)
+{
+ struct device_node *guts_np;
+ struct resource res;
+
+ pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
+
+ /* Get the physical address of the global utilities registers */
+ guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
+ if (of_address_to_resource(guts_np, 0, &res)) {
+ pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
+ return -EINVAL;
+ }
+ guts_phys = res.start;
+
+ return platform_driver_register(&mpc8610_hpcd_driver);
+}
+
+/**
+ * mpc8610_hpcd_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit mpc8610_hpcd_exit(void)
+{
+ platform_driver_unregister(&mpc8610_hpcd_driver);
+}
+
+module_init(mpc8610_hpcd_init);
+module_exit(mpc8610_hpcd_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
--
1.6.5
^ permalink raw reply related
* [PATCH 1/2] powerpc/8610: add probing for individual DMA channels, not just DMA controllers
From: Timur Tabi @ 2010-05-03 21:54 UTC (permalink / raw)
To: kumar.gala, linuxppc-dev, alsa-devel, broonie, lrg
A future version of the MPC8610 HPCD's ASoC DMA driver will probe on individual
DMA channel nodes, so the DMA controller nodes' compatible string must be listed
in mpc8610_ids[] for the probe to work.
Also remove the "gianfar" compatible from mpc8610_ids[], since there is no
gianfar (or any other networking device) on the 8610.
Signed-off-by: Timur Tabi <timur@freescale.com>
---
Kumar, the ASoC mainters are willing to pick up this patch, but they want an
ACK from you first. Or, you could pick it up, since by itself it's harmless.
arch/powerpc/platforms/86xx/mpc8610_hpcd.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
index 5abe137..018cc67 100644
--- a/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
+++ b/arch/powerpc/platforms/86xx/mpc8610_hpcd.c
@@ -83,7 +83,8 @@ static struct of_device_id __initdata mpc8610_ids[] = {
{ .compatible = "fsl,mpc8610-immr", },
{ .compatible = "fsl,mpc8610-guts", },
{ .compatible = "simple-bus", },
- { .compatible = "gianfar", },
+ /* So that the DMA channel nodes can be probed individually: */
+ { .compatible = "fsl,eloplus-dma", },
{}
};
--
1.6.5
^ permalink raw reply related
* Re: [PATCH v21 001/100] eclone (1/11): Factor out code to allocate pidmap page
From: David Miller @ 2010-05-03 21:12 UTC (permalink / raw)
To: dave
Cc: linux-s390, orenl, containers, x86, linux-kernel, linuxppc-dev,
matthltc, linux-api, serue, akpm, sukadev, xemul
In-Reply-To: <1272920551.12034.4729.camel@nimitz>
From: Dave Hansen <dave@linux.vnet.ibm.com>
Date: Mon, 03 May 2010 14:02:31 -0700
> It has implications _everywhere_.
That does not remove the responsibility to break things up into
managable pieces, not does it make such a task impossible or
even hard to do.
You post sets of 10 to 15 at a time, once those are agreed to
and to everyone's general liking, you toss them into a GIT tree
and you say "here's the next 10 to 15 and they are relative to
the changes in GIT tree X which have already been fully reviewed"
And so on and so forth.
And this is the only logical thing to do, because if someone wants
a change in patch 7, it can effect patch 23 so it's pointless to
post for review a patch that's going to end up changing anyways.
That's a waste of reviewer resources.
To be honest, I'm really tired of what tends to be people's knee jerk
reaction to this situations, which is a lot of people doing nothing
but defending themselves. Even if it did not violate documented
policy (it did), it violates common sense. So, can people do
something more constructive than trying to defend themselves on this?
It's stupid and shouldn't have been done, and we should move on.
^ 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