diff --git a/Documentation/devicetree/bindings/arm/msm/lpm-levels.txt b/Documentation/devicetree/bindings/arm/msm/lpm-levels.txt index 3ca6a36116276..976c2f6608818 100644 --- a/Documentation/devicetree/bindings/arm/msm/lpm-levels.txt +++ b/Documentation/devicetree/bindings/arm/msm/lpm-levels.txt @@ -71,8 +71,6 @@ Required properties: sleep. - qcom,last-level - When set, the cluster level is applied only when there is 1 online core. - - qcom,disable-dynamic-int-routing: When set disables the dynamic - routing of rpm-smd and mpm interrupts to next wake up core. [Node bindings for qcom,pm-cpu] qcom,pm-cpu contains the low power modes that a cpu could enter. Currently it diff --git a/Documentation/devicetree/bindings/arm/msm/lpm-workarounds.txt b/Documentation/devicetree/bindings/arm/msm/lpm-workarounds.txt index 0d420621acbf4..bedfed5e5ccd8 100644 --- a/Documentation/devicetree/bindings/arm/msm/lpm-workarounds.txt +++ b/Documentation/devicetree/bindings/arm/msm/lpm-workarounds.txt @@ -5,11 +5,6 @@ The required properties are: - compatible: "qcom,lpm-workarounds" The optional properties are: -- reg: The physical address and the size of the l1_l2_gcc and l2_pwr_sts - regitsters of performance cluster. - -- reg-names: "l2_pwr_sts" - string to identify l2_pwr_sts physical address. - "l1_l2_gcc" - string to identify l1_l2_gcc physical address. - qcom,lpm-wa-cx-turbo-unvote: Indicates the workaround to unvote CX turbo vote when system is coming out of rpm assisted power collaspe. @@ -18,36 +13,11 @@ The optional properties are: - lpm-cx-supply: will hold handle for CX regulator supply which is used to unvote. -- qcom,lpm-wa-skip-l2-spm: Due to a hardware bug on 8939 and 8909, secure - world needs to disable and enable L2 SPM to get the proper context - in secure watchdog bite cases. With this workaround there is a race - in programming L2 SPM between HLOS and secure world. This leads to - stability issues. To avoid this program L2 SPM only in secure world - based on the L2 mode flag passed. Set lpm-wa-skip-l2-spm node if this - is required. - -- qcom,lpm-wa-dynamic-clock-gating: Due to a hardware bug on 8952, L1/L2 dynamic - clock gating needs to be enabled by software for performance cluster - cores and L2. Set lpm-wa-dynamic-clock-gating node if this workaround is - required. - -- qcom,cpu-offline-mask: Dynamic clock gating should be enabled when cluster is - in L2 PC. Each bit of cpu-offline-mask lists the cpu no. to hotplug by KTM - driver. - -- qcom,non-boot-cpu-index: will hold index of non boot cluster cpu. - Example: qcom,lpm-workarounds { compatible = "qcom,lpm-workarounds"; - reg = <0x0B011018 0x4>, - <0x0B011088 0x4>; - reg-names = "l2-pwr-sts", "l1-l2-gcc"; + lpm-cx-supply = <&pm8916_s2_corner>; qcom,lpm-wa-cx-turbo-unvote; - qcom,lpm-wa-skip-l2-spm; - qcom,lpm-wa-dynamic-clock-gating; - qcom,cpu-offline-mask = "0xF"; - qcom,non-boot-cpu-index = <4>; } diff --git a/Makefile b/Makefile index 7ac661f81236a..48437d4dedd73 100755 --- a/Makefile +++ b/Makefile @@ -380,7 +380,7 @@ CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ CFLAGS_MODULE = $(FLAGS_OPTIMIZE) $(GRAPHITE) AFLAGS_MODULE = $(FLAGS_OPTIMIZE) $(GRAPHITE) LDFLAGS_MODULE = --strip-debug -CFLAGS_KERNEL = $(FLAGS_OPTIMIZE) $(GRAPHITE) -mcpu=cortex-a57.cortex-a53+crypto+crc -mtune=cortex-a57.cortex-a53 -march=armv8-a+crypto+crc +CFLAGS_KERNEL = $(FLAGS_OPTIMIZE) $(GRAPHITE) -mcpu=cortex-a57.cortex-a53+crypto+crc -mtune=cortex-a57.cortex-a53 -march=armv8-a+crypto+crc -mfix-cortex-a53-843419 -mfix-cortex-a53-835769 AFLAGS_KERNEL = $(FLAGS_OPTIMIZE) $(GRAPHITE) CFLAGS_GCOV = -fprofile-arcs -ftest-coverage @@ -437,6 +437,8 @@ KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \ -Wno-switch-bool \ -Wno-multistatement-macros \ -Wno-bool-operation -Wno-nonnull -Wno-switch-unreachable -Wno-format-truncation -Wno-format-overflow -Wno-duplicate-decl-specifier -Wno-memset-elt-size -Wno-int-in-bool-context \ + -mstrict-align \ + -mfix-cortex-a53-843419 -mfix-cortex-a53-835769 \ $(GEN_OPT_FLAGS) \ $(GRAPHITE) \ $(FLAGS_OPTIMIZE) diff --git a/android/configs/android-recommended.cfg b/android/configs/android-recommended.cfg index 960b9de2860d2..4dd3b4585033d 100644 --- a/android/configs/android-recommended.cfg +++ b/android/configs/android-recommended.cfg @@ -109,6 +109,10 @@ CONFIG_TABLET_USB_GTCO=y CONFIG_TABLET_USB_HANWANG=y CONFIG_TABLET_USB_KBTAB=y CONFIG_TABLET_USB_WACOM=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_TASK_XACCT=y CONFIG_TIMER_STATS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y diff --git a/arch/arm/boot/dts/qcom/msm-pmi8994.dtsi b/arch/arm/boot/dts/qcom/msm-pmi8994.dtsi index 94da8ca6e1b03..76dde833f6893 100644 --- a/arch/arm/boot/dts/qcom/msm-pmi8994.dtsi +++ b/arch/arm/boot/dts/qcom/msm-pmi8994.dtsi @@ -608,6 +608,8 @@ label = "rgb"; status = "okay"; + qcom,rgb-sync; + qcom,rgb_0 { label = "rgb"; qcom,id = <3>; diff --git a/arch/arm/boot/dts/qcom/msm8994.dtsi b/arch/arm/boot/dts/qcom/msm8994.dtsi index 93a1cc7a60038..297fdc5c32b63 100644 --- a/arch/arm/boot/dts/qcom/msm8994.dtsi +++ b/arch/arm/boot/dts/qcom/msm8994.dtsi @@ -37,9 +37,17 @@ dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/system"; type = "ext4"; mnt_flags = "ro,barrier=1,discard"; - fsmgr_flags = "wait"; + fsmgr_flags = "wait,verify=/dev/block/platform/soc.0/f9824900.sdhci/by-name/reserve2"; status = "ok"; }; +vendor { + compatible = "android,vendor"; + dev = "/dev/block/platform/soc.0/f9824900.sdhci/by-name/cache"; + type = "ext4"; + mnt_flags = "ro,barrier=1,discard"; + fsmgr_flags = "wait,verify=/dev/block/platform/soc.0/f9824900.sdhci/by-name/reserve2"; + status = "ok"; + }; }; }; }; diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index d2465a107f889..0b9d771ddbe57 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -160,6 +160,8 @@ static void arm_dma_unremap(struct device *dev, void *remapped_addr, return; } vunmap(remapped_addr); + flush_tlb_kernel_range((unsigned long)remapped_addr, + (unsigned long)(remapped_addr + size)); } struct dma_map_ops arm_dma_ops = { diff --git a/arch/arm64/configs/haruhi_defconfig b/arch/arm64/configs/haruhi_defconfig index 13034144f363d..f8127a95cb7f7 100644 --- a/arch/arm64/configs/haruhi_defconfig +++ b/arch/arm64/configs/haruhi_defconfig @@ -598,14 +598,22 @@ CONFIG_DEFAULT_WESTWOOD=y CONFIG_DEFAULT_TCP_CONG="westwood" CONFIG_WIREGUARD=y # CONFIG_WIREGUARD_DEBUG is not set -CONFIG_FB_MSM_MDSS_KCAL_CTRL=y CONFIG_CRYPTO_SHA256_ARM64=y CONFIG_CRYPTO_SHA512_ARM64=y CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y -CONFIG_QUICK_WAKEUP=y -CONFIG_POWERSUSPEND=y # CONFIG_ARM_ARCH_TIMER_VCT_ACCESS is not set # CONFIG_TCP_CONG_BIC is not set # CONFIG_TCP_CONG_CUBIC is not set # CONFIG_TCP_CONG_HTCP is not set CONFIG_EXFAT_FS=y +CONFIG_SDFAT_FS=y +CONFIG_SDFAT_DELAYED_META_DIRTY=y +CONFIG_SDFAT_SUPPORT_DIR_SYNC=y +CONFIG_SDFAT_DEFAULT_CODEPAGE=437 +CONFIG_SDFAT_DEFAULT_IOCHARSET="utf8" +CONFIG_SDFAT_ALIGNED_MPAGE_WRITE=y +CONFIG_SDFAT_VIRTUAL_XATTR=y +CONFIG_SDFAT_VIRTUAL_XATTR_SELINUX_LABEL="u:object_r:vfat:s0" +CONFIG_SDFAT_DEBUG=y +CONFIG_SDFAT_DBG_MSG=y +CONFIG_SDFAT_STATISTICS=y diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c index 2678f6e17db5c..01d9e5c503469 100644 --- a/arch/arm64/mm/dma-mapping.c +++ b/arch/arm64/mm/dma-mapping.c @@ -506,6 +506,8 @@ static void arm64_dma_unremap(struct device *dev, void *remapped_addr, return; } vunmap(remapped_addr); + flush_tlb_kernel_range((unsigned long)remapped_addr, + (unsigned long)(remapped_addr + size)); } const struct dma_map_ops noncoherent_swiotlb_dma_ops = { diff --git a/crypto/hmac.c b/crypto/hmac.c index ade790b454e99..758688b4bc6b4 100644 --- a/crypto/hmac.c +++ b/crypto/hmac.c @@ -197,11 +197,15 @@ static int hmac_create(struct crypto_template *tmpl, struct rtattr **tb) salg = shash_attr_alg(tb[1], 0, 0); if (IS_ERR(salg)) return PTR_ERR(salg); + alg = &salg->base; + /* The underlying hash algorithm must be unkeyed */ err = -EINVAL; + if (crypto_shash_alg_has_setkey(salg)) + goto out_put_alg; + ds = salg->digestsize; ss = salg->statesize; - alg = &salg->base; if (ds > alg->cra_blocksize || ss < alg->cra_blocksize) goto out_put_alg; diff --git a/crypto/shash.c b/crypto/shash.c index ac4d76350d1b7..956979787efb7 100644 --- a/crypto/shash.c +++ b/crypto/shash.c @@ -24,11 +24,12 @@ static const struct crypto_type crypto_shash_type; -static int shash_no_setkey(struct crypto_shash *tfm, const u8 *key, - unsigned int keylen) +int shash_no_setkey(struct crypto_shash *tfm, const u8 *key, + unsigned int keylen) { return -ENOSYS; } +EXPORT_SYMBOL_GPL(shash_no_setkey); static int shash_setkey_unaligned(struct crypto_shash *tfm, const u8 *key, unsigned int keylen) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 921c0e93166aa..e2e2a99ff3bd1 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -50,6 +50,8 @@ #include #include "binder_trace.h" +#define BINDER_MIN_ALLOC (1 * PAGE_SIZE) + static HLIST_HEAD(binder_devices); static struct dentry *binder_debugfs_dir_entry_root; @@ -322,7 +324,7 @@ struct binder_buffer { size_t data_size; size_t offsets_size; size_t extra_buffers_size; - uint8_t data[0]; + void *data; }; enum binder_deferred_state { @@ -558,13 +560,23 @@ static void binder_set_nice(long nice) binder_user_error("%d RLIMIT_NICE not set\n", current->pid); } +static struct binder_buffer *binder_buffer_next(struct binder_buffer *buffer) +{ + return list_entry(buffer->entry.next, struct binder_buffer, entry); +} + +static struct binder_buffer *binder_buffer_prev(struct binder_buffer *buffer) +{ + return list_entry(buffer->entry.prev, struct binder_buffer, entry); +} + static size_t binder_buffer_size(struct binder_proc *proc, struct binder_buffer *buffer) { if (list_is_last(&buffer->entry, &proc->buffers)) - return proc->buffer + proc->buffer_size - (void *)buffer->data; - return (size_t)list_entry(buffer->entry.next, - struct binder_buffer, entry) - (size_t)buffer->data; + return (u8 *)proc->buffer + + proc->buffer_size - (u8 *)buffer->data; + return (u8 *)binder_buffer_next(buffer)->data - (u8 *)buffer->data; } static void binder_insert_free_buffer(struct binder_proc *proc, @@ -614,9 +626,9 @@ static void binder_insert_allocated_buffer(struct binder_proc *proc, buffer = rb_entry(parent, struct binder_buffer, rb_node); BUG_ON(buffer->free); - if (new_buffer < buffer) + if (new_buffer->data < buffer->data) p = &parent->rb_left; - else if (new_buffer > buffer) + else if (new_buffer->data > buffer->data) p = &parent->rb_right; else BUG(); @@ -630,18 +642,17 @@ static struct binder_buffer *binder_buffer_lookup(struct binder_proc *proc, { struct rb_node *n = proc->allocated_buffers.rb_node; struct binder_buffer *buffer; - struct binder_buffer *kern_ptr; + void *kern_ptr; - kern_ptr = (struct binder_buffer *)(user_ptr - proc->user_buffer_offset - - offsetof(struct binder_buffer, data)); + kern_ptr = (void *)(user_ptr - proc->user_buffer_offset); while (n) { buffer = rb_entry(n, struct binder_buffer, rb_node); BUG_ON(buffer->free); - if (kern_ptr < buffer) + if (kern_ptr < buffer->data) n = n->rb_left; - else if (kern_ptr > buffer) + else if (kern_ptr > buffer->data) n = n->rb_right; else return buffer; @@ -649,9 +660,9 @@ static struct binder_buffer *binder_buffer_lookup(struct binder_proc *proc, return NULL; } -static int binder_update_page_range(struct binder_proc *proc, int allocate, - void *start, void *end, - struct vm_area_struct *vma) +static int __binder_update_page_range(struct binder_proc *proc, int allocate, + void *start, void *end, + struct vm_area_struct *vma) { void *page_addr; unsigned long user_page_addr; @@ -761,6 +772,20 @@ static int binder_update_page_range(struct binder_proc *proc, int allocate, return -ENOMEM; } +static int binder_update_page_range(struct binder_proc *proc, int allocate, + void *start, void *end, + struct vm_area_struct *vma) +{ + /* + * For regular updates, move up start if needed since MIN_ALLOC pages + * are always mapped + */ + if (start - proc->buffer < BINDER_MIN_ALLOC) + start = proc->buffer + BINDER_MIN_ALLOC; + + return __binder_update_page_range(proc, allocate, start, end, vma); +} + static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, size_t data_size, size_t offsets_size, @@ -803,6 +828,9 @@ static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, return NULL; } + /* Pad 0-size buffers so they get assigned unique addresses */ + size = max(size, sizeof(void *)); + while (n) { buffer = rb_entry(n, struct binder_buffer, rb_node); BUG_ON(!buffer->free); @@ -834,30 +862,32 @@ static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, has_page_addr = (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK); - if (n == NULL) { - if (size + sizeof(struct binder_buffer) + 4 >= buffer_size) - buffer_size = size; /* no room for other buffers */ - else - buffer_size = size + sizeof(struct binder_buffer); - } + WARN_ON(n && buffer_size != size); end_page_addr = - (void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size); + (void *)PAGE_ALIGN((uintptr_t)buffer->data + size); if (end_page_addr > has_page_addr) end_page_addr = has_page_addr; if (binder_update_page_range(proc, 1, (void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL)) return NULL; - - rb_erase(best_fit, &proc->free_buffers); - buffer->free = 0; - binder_insert_allocated_buffer(proc, buffer); if (buffer_size != size) { - struct binder_buffer *new_buffer = (void *)buffer->data + size; + struct binder_buffer *new_buffer; + new_buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!new_buffer) { + pr_err("%s: %d failed to alloc new buffer struct\n", + __func__, proc->pid); + goto err_alloc_buf_struct_failed; + } + new_buffer->data = (u8 *)buffer->data + size; list_add(&new_buffer->entry, &buffer->entry); new_buffer->free = 1; binder_insert_free_buffer(proc, new_buffer); } + + rb_erase(best_fit, &proc->free_buffers); + buffer->free = 0; + binder_insert_allocated_buffer(proc, buffer); binder_debug(BINDER_DEBUG_BUFFER_ALLOC, "%d: binder_alloc_buf size %zd got %pK\n", proc->pid, size, buffer); @@ -873,61 +903,70 @@ static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, } return buffer; + +err_alloc_buf_struct_failed: + binder_update_page_range(proc, 0, + (void *)PAGE_ALIGN((uintptr_t)buffer->data), + end_page_addr, NULL); + return NULL; } static void *buffer_start_page(struct binder_buffer *buffer) { - return (void *)((uintptr_t)buffer & PAGE_MASK); + return (void *)((uintptr_t)buffer->data & PAGE_MASK); } -static void *buffer_end_page(struct binder_buffer *buffer) +static void *prev_buffer_end_page(struct binder_buffer *buffer) { - return (void *)(((uintptr_t)(buffer + 1) - 1) & PAGE_MASK); + return (void *)(((uintptr_t)(buffer->data) - 1) & PAGE_MASK); } static void binder_delete_free_buffer(struct binder_proc *proc, struct binder_buffer *buffer) { struct binder_buffer *prev, *next = NULL; - int free_page_end = 1; - int free_page_start = 1; + bool to_free = true; BUG_ON(proc->buffers.next == &buffer->entry); - prev = list_entry(buffer->entry.prev, struct binder_buffer, entry); + prev = binder_buffer_prev(buffer); BUG_ON(!prev->free); - if (buffer_end_page(prev) == buffer_start_page(buffer)) { - free_page_start = 0; - if (buffer_end_page(prev) == buffer_end_page(buffer)) - free_page_end = 0; + if (prev_buffer_end_page(prev) == buffer_start_page(buffer)) { + to_free = false; binder_debug(BINDER_DEBUG_BUFFER_ALLOC, "%d: merge free, buffer %pK share page with %pK\n", - proc->pid, buffer, prev); + proc->pid, buffer->data, prev->data); } if (!list_is_last(&buffer->entry, &proc->buffers)) { - next = list_entry(buffer->entry.next, - struct binder_buffer, entry); - if (buffer_start_page(next) == buffer_end_page(buffer)) { - free_page_end = 0; - if (buffer_start_page(next) == - buffer_start_page(buffer)) - free_page_start = 0; + next = binder_buffer_next(buffer); + if (buffer_start_page(next) == buffer_start_page(buffer)) { + to_free = false; binder_debug(BINDER_DEBUG_BUFFER_ALLOC, "%d: merge free, buffer %pK share page with %pK\n", - proc->pid, buffer, prev); + proc->pid, + buffer->data, + next->data); } } - list_del(&buffer->entry); - if (free_page_start || free_page_end) { + + if (PAGE_ALIGNED(buffer->data)) { + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "%d: merge free, buffer start %pK is page aligned\n", + proc->pid, buffer->data); + to_free = false; + } + + if (to_free) { binder_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: merge free, buffer %pK do not share page%s%s with %pK or %pK\n", - proc->pid, buffer, free_page_start ? "" : " end", - free_page_end ? "" : " start", prev, next); - binder_update_page_range(proc, 0, free_page_start ? - buffer_start_page(buffer) : buffer_end_page(buffer), - (free_page_end ? buffer_end_page(buffer) : - buffer_start_page(buffer)) + PAGE_SIZE, NULL); + "%d: merge free, buffer %pK do not share page with %pK or %pK\n", + proc->pid, buffer->data, + prev->data, next->data); + binder_update_page_range(proc, 0, buffer_start_page(buffer), + buffer_start_page(buffer) + PAGE_SIZE, + NULL); } + list_del(&buffer->entry); + kfree(buffer); } static void binder_free_buf(struct binder_proc *proc, @@ -948,8 +987,8 @@ static void binder_free_buf(struct binder_proc *proc, BUG_ON(buffer->free); BUG_ON(size > buffer_size); BUG_ON(buffer->transaction != NULL); - BUG_ON((void *)buffer < proc->buffer); - BUG_ON((void *)buffer > proc->buffer + proc->buffer_size); + BUG_ON(buffer->data < proc->buffer); + BUG_ON(buffer->data > proc->buffer + proc->buffer_size); if (buffer->async_transaction) { proc->free_async_space += size + sizeof(struct binder_buffer); @@ -966,8 +1005,7 @@ static void binder_free_buf(struct binder_proc *proc, rb_erase(&buffer->rb_node, &proc->allocated_buffers); buffer->free = 1; if (!list_is_last(&buffer->entry, &proc->buffers)) { - struct binder_buffer *next = list_entry(buffer->entry.next, - struct binder_buffer, entry); + struct binder_buffer *next = binder_buffer_next(buffer); if (next->free) { rb_erase(&next->rb_node, &proc->free_buffers); @@ -975,8 +1013,7 @@ static void binder_free_buf(struct binder_proc *proc, } } if (proc->buffers.next != &buffer->entry) { - struct binder_buffer *prev = list_entry(buffer->entry.prev, - struct binder_buffer, entry); + struct binder_buffer *prev = binder_buffer_prev(buffer); if (prev->free) { binder_delete_free_buffer(proc, buffer); @@ -3519,6 +3556,11 @@ static int binder_mmap(struct file *filp, struct vm_area_struct *vma) } } #endif + if (vma->vm_end - vma->vm_start < BINDER_MIN_ALLOC) { + ret = -EINVAL; + failure_string = "VMA size < BINDER_MIN_ALLOC"; + goto err_vma_too_small; + } proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL); if (proc->pages == NULL) { ret = -ENOMEM; @@ -3530,17 +3572,24 @@ static int binder_mmap(struct file *filp, struct vm_area_struct *vma) vma->vm_ops = &binder_vm_ops; vma->vm_private_data = proc; + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + failure_string = "alloc buffer struct"; + goto err_alloc_buf_struct_failed; + } + /* binder_update_page_range assumes preemption is disabled */ preempt_disable(); - ret = binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma); + ret = __binder_update_page_range(proc, 1, proc->buffer, + proc->buffer + BINDER_MIN_ALLOC, vma); preempt_enable_no_resched(); if (ret) { ret = -ENOMEM; failure_string = "alloc small buf"; goto err_alloc_small_buf_failed; } - buffer = proc->buffer; - INIT_LIST_HEAD(&proc->buffers); + buffer->data = proc->buffer; list_add(&buffer->entry, &proc->buffers); buffer->free = 1; binder_insert_free_buffer(proc, buffer); @@ -3555,9 +3604,12 @@ static int binder_mmap(struct file *filp, struct vm_area_struct *vma) return 0; err_alloc_small_buf_failed: + kfree(buffer); +err_alloc_buf_struct_failed: kfree(proc->pages); proc->pages = NULL; err_alloc_pages_failed: +err_vma_too_small: mutex_lock(&proc->context->binder_mmap_lock); vfree(proc->buffer); proc->buffer = NULL; @@ -3586,6 +3638,7 @@ static int binder_open(struct inode *nodp, struct file *filp) INIT_LIST_HEAD(&proc->todo); init_waitqueue_head(&proc->wait); proc->default_priority = task_nice(current); + INIT_LIST_HEAD(&proc->buffers); binder_dev = container_of(filp->private_data, struct binder_device, miscdev); proc->context = &binder_dev->context; @@ -3710,6 +3763,7 @@ static void binder_deferred_release(struct binder_proc *proc) struct binder_transaction *t; struct binder_context *context = proc->context; struct rb_node *n; + struct binder_buffer *buffer; int threads, nodes, incoming_refs, outgoing_refs, buffers, active_transactions, page_count; @@ -3762,8 +3816,6 @@ static void binder_deferred_release(struct binder_proc *proc) buffers = 0; while ((n = rb_first(&proc->allocated_buffers))) { - struct binder_buffer *buffer; - buffer = rb_entry(n, struct binder_buffer, rb_node); t = buffer->transaction; @@ -3781,6 +3833,16 @@ static void binder_deferred_release(struct binder_proc *proc) binder_stats_deleted(BINDER_STAT_PROC); + while (!list_empty(&proc->buffers)) { + buffer = list_first_entry(&proc->buffers, + struct binder_buffer, entry); + WARN_ON(!buffer->free); + + list_del(&buffer->entry); + WARN_ON_ONCE(!list_empty(&proc->buffers)); + kfree(buffer); + } + page_count = 0; if (proc->pages) { int i; diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index eb3575b3fbf08..33788cb1ffc64 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -1796,11 +1796,11 @@ static void show_floppy(void) if (work_pending(&floppy_work)) pr_info("floppy_work.func=%pf\n", floppy_work.func); if (delayed_work_pending(&fd_timer)) - pr_info("delayed work.function=%p expires=%ld\n", + pr_info("delayed work.function=%pf expires=%ld\n", fd_timer.work.func, fd_timer.timer.expires - jiffies); if (delayed_work_pending(&fd_timeout)) - pr_info("timer_function=%p expires=%ld\n", + pr_info("timer_function=%pf expires=%ld\n", fd_timeout.work.func, fd_timeout.timer.expires - jiffies); @@ -3445,6 +3445,7 @@ static int fd_locked_ioctl(struct block_device *bdev, fmode_t mode, unsigned int (struct floppy_struct **)&outparam); if (ret) return ret; + size = offsetof(struct floppy_struct, name); break; case FDMSGON: UDP->flags |= FTD_MSG; diff --git a/drivers/char/adsprpc.c b/drivers/char/adsprpc.c index 815d822565a7f..e08f4c6d65f56 100644 --- a/drivers/char/adsprpc.c +++ b/drivers/char/adsprpc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2015, 2018 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -62,6 +62,9 @@ up_read(¤t->mm->mmap_sem);\ } while (0) +#define FASTRPC_CTX_MAGIC (0xbeeddeed) +#define FASTRPC_CTX_MAX (256) +#define FASTRPC_CTXID_MASK (0xFF0) #define IS_CACHE_ALIGNED(x) (((x) & ((L1_CACHE_BYTES)-1)) == 0) @@ -77,7 +80,7 @@ static inline uintptr_t buf_page_offset(void *buf) return offset; } -static inline int buf_num_pages(void *buf, ssize_t len) +static inline int buf_num_pages(void *buf, size_t len) { uintptr_t start = buf_page_start(buf) >> PAGE_SHIFT; uintptr_t end = (((uintptr_t) buf + len - 1) & PAGE_MASK) >> PAGE_SHIFT; @@ -91,6 +94,13 @@ static inline uint32_t buf_page_size(uint32_t size) return sz > PAGE_SIZE ? sz : PAGE_SIZE; } +static inline uint64_t ptr_to_uint64(void *ptr) +{ + uint64_t addr = (uint64_t)((uintptr_t)ptr); + + return addr; +} + static inline int buf_get_pages(void *addr, ssize_t sz, int nr_pages, int access, struct smq_phy_page *pages, int nr_elems, struct smq_phy_page *range) @@ -142,7 +152,7 @@ struct fastrpc_buf { struct ion_handle *handle; void *virt; ion_phys_addr_t phys; - ssize_t size; + size_t size; int used; }; @@ -178,6 +188,8 @@ struct smq_invoke_ctx { uint32_t sc; struct overlap *overs; struct overlap **overps; + unsigned int magic; + uint64_t ctxid; }; struct smq_context_list { @@ -216,6 +228,8 @@ struct fastrpc_apps { spinlock_t wrlock; spinlock_t hlock; struct hlist_head htbl[RPC_HASH_SZ]; + spinlock_t ctxlock; + struct smq_invoke_ctx *ctxtable[FASTRPC_CTX_MAX]; }; struct fastrpc_mmap { @@ -225,7 +239,7 @@ struct fastrpc_mmap { ion_phys_addr_t phys; uintptr_t *vaddrin; uintptr_t vaddrout; - ssize_t size; + size_t size; int refs; }; @@ -273,7 +287,7 @@ static int map_iommu_mem(struct ion_handle *handle, struct file_data *fdata, ion_phys_addr_t *iova, unsigned long size) { struct fastrpc_apps *me = &gfa; - struct fastrpc_mmap *map = 0, *mapmatch = 0; + struct fastrpc_mmap *map = NULL, *mapmatch = NULL; struct hlist_node *n; unsigned long len = size; int cid = fdata->cid; @@ -307,7 +321,7 @@ static void unmap_iommu_mem(struct ion_handle *handle, struct file_data *fdata, int cached) { struct fastrpc_apps *me = &gfa; - struct fastrpc_mmap *map = 0, *mapmatch = 0; + struct fastrpc_mmap *map = NULL, *mapmatch = NULL; struct hlist_node *n; int cid = fdata->cid; @@ -342,10 +356,10 @@ static void free_mem(struct fastrpc_buf *buf, struct file_data *fd) } if (!IS_ERR_OR_NULL(buf->virt)) { ion_unmap_kernel(me->iclient, buf->handle); - buf->virt = 0; + buf->virt = NULL; } ion_free(me->iclient, buf->handle); - buf->handle = 0; + buf->handle = NULL; } } @@ -361,11 +375,11 @@ static void free_map(struct fastrpc_mmap *map, struct file_data *fdata) } if (!IS_ERR_OR_NULL(map->virt)) { ion_unmap_kernel(me->iclient, map->handle); - map->virt = 0; + map->virt = NULL; } ion_free(me->iclient, map->handle); } - map->handle = 0; + map->handle = NULL; } static int alloc_mem(struct fastrpc_buf *buf, struct file_data *fdata) @@ -376,8 +390,8 @@ static int alloc_mem(struct fastrpc_buf *buf, struct file_data *fdata) int err = 0; int cid = fdata->cid; unsigned int heap; - buf->handle = 0; - buf->virt = 0; + buf->handle = NULL; + buf->virt = NULL; buf->phys = 0; heap = me->channel[cid].smmu.enabled ? ION_HEAP(ION_IOMMU_HEAP_ID) : ION_HEAP(ION_ADSP_HEAP_ID); @@ -412,7 +426,7 @@ static int context_restore_interrupted(struct fastrpc_apps *me, struct smq_invoke_ctx **po) { int err = 0; - struct smq_invoke_ctx *ctx = 0, *ictx = 0; + struct smq_invoke_ctx *ctx = NULL, *ictx = NULL; struct hlist_node *n; struct fastrpc_ioctl_invoke *invoke = &invokefd->inv; spin_lock(&me->clst.hlock); @@ -507,9 +521,10 @@ static int context_alloc(struct fastrpc_apps *me, uint32_t kernel, struct file_data *fdata, struct smq_invoke_ctx **po) { - int err = 0, bufs, size = 0; - struct smq_invoke_ctx *ctx = 0; + int err = 0, bufs, ii, size = 0; + struct smq_invoke_ctx *ctx = NULL; struct smq_context_list *clst = &me->clst; + struct fastrpc_ioctl_invoke *invoke = &invokefd->inv; bufs = REMOTE_SCALARS_INBUFS(invoke->sc) + @@ -521,7 +536,7 @@ static int context_alloc(struct fastrpc_apps *me, uint32_t kernel, bufs * sizeof(*ctx->handles); } - VERIFY(err, 0 != (ctx = kzalloc(sizeof(*ctx) + size, GFP_KERNEL))); + VERIFY(err, NULL != (ctx = kzalloc(sizeof(*ctx) + size, GFP_KERNEL))); if (err) goto bail; @@ -530,8 +545,8 @@ static int context_alloc(struct fastrpc_apps *me, uint32_t kernel, ctx->apps = me; ctx->fdata = fdata; ctx->pra = (remote_arg_t *)(&ctx[1]); - ctx->fds = invokefd->fds == 0 ? 0 : (int *)(&ctx->pra[bufs]); - ctx->handles = invokefd->fds == 0 ? 0 : + ctx->fds = invokefd->fds == NULL ? NULL : (int *)(&ctx->pra[bufs]); + ctx->handles = invokefd->fds == NULL ? NULL : (struct ion_handle **)(&ctx->fds[bufs]); if (!kernel) { VERIFY(err, 0 == copy_from_user(ctx->pra, invoke->pra, @@ -563,10 +578,26 @@ static int context_alloc(struct fastrpc_apps *me, uint32_t kernel, ctx->pid = current->pid; ctx->tgid = current->tgid; init_completion(&ctx->work); + ctx->magic = FASTRPC_CTX_MAGIC; spin_lock(&clst->hlock); hlist_add_head(&ctx->hn, &clst->pending); spin_unlock(&clst->hlock); + spin_lock(&me->ctxlock); + for (ii = 0; ii < FASTRPC_CTX_MAX; ii++) { + if (!me->ctxtable[ii]) { + me->ctxtable[ii] = ctx; + ctx->ctxid = (ptr_to_uint64(ctx) & ~0xFFF)|(ii << 4); + break; + } + } + spin_unlock(&me->ctxlock); + VERIFY(err, ii < FASTRPC_CTX_MAX); + if (err) { + pr_err("adsprpc: out of context memory\n"); + goto bail; + } + *po = ctx; bail: if (ctx && err) @@ -594,6 +625,7 @@ static void context_free(struct smq_invoke_ctx *ctx, int remove) int ssrcount = ctx->fdata->ssrcount; struct fastrpc_smmu *smmu = &apps->channel[cid].smmu; struct fastrpc_buf *b; + struct fastrpc_apps *me = &gfa; int i, bufs; if (ctx->smmu) { bufs = REMOTE_SCALARS_INBUFS(ctx->sc) + @@ -615,6 +647,17 @@ static void context_free(struct smq_invoke_ctx *ctx, int remove) free_mem(b, ctx->fdata); kfree(ctx->abufs); + ctx->magic = 0; + ctx->ctxid = 0; + + spin_lock(&me->ctxlock); + for (i = 0; i < FASTRPC_CTX_MAX; i++) { + if (me->ctxtable[i] == ctx) { + me->ctxtable[i] = NULL; + break; + } + } + spin_unlock(&me->ctxlock); if (ctx->dev) { add_dev(apps, ctx->dev); if (ctx->obuf.handle != ctx->dev->buf.handle) @@ -638,7 +681,7 @@ static void context_notify_user(struct smq_invoke_ctx *ctx, int retval) static void context_notify_all_users(struct smq_context_list *me, int cid) { - struct smq_invoke_ctx *ictx = 0; + struct smq_invoke_ctx *ictx = NULL; struct hlist_node *n; spin_lock(&me->hlock); hlist_for_each_entry_safe(ictx, n, &me->pending, hn) { @@ -663,10 +706,10 @@ static void context_list_ctor(struct smq_context_list *me) static void context_list_dtor(struct fastrpc_apps *me, struct smq_context_list *clst) { - struct smq_invoke_ctx *ictx = 0, *ctxfree; + struct smq_invoke_ctx *ictx = NULL, *ctxfree; struct hlist_node *n; do { - ctxfree = 0; + ctxfree = NULL; spin_lock(&clst->hlock); hlist_for_each_entry_safe(ictx, n, &clst->interrupted, hn) { hlist_del(&ictx->hn); @@ -699,7 +742,7 @@ static int get_page_list(uint32_t kernel, struct smq_invoke_ctx *ctx) struct fastrpc_buf *ibuf = &ctx->dev->buf; struct fastrpc_buf *obuf = &ctx->obuf; remote_arg_t *pra = ctx->pra; - ssize_t rlen; + size_t rlen; uint32_t sc = ctx->sc; int cid = ctx->fdata->cid; int i, err = 0; @@ -726,7 +769,7 @@ static int get_page_list(uint32_t kernel, struct smq_invoke_ctx *ctx) for (i = 0; i < inbufs + outbufs; ++i) { void *buf; int num; - ssize_t len; + size_t len; list[i].num = 0; list[i].pgidx = 0; @@ -833,14 +876,14 @@ static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx, { struct fastrpc_apps *me = &gfa; struct smq_invoke_buf *list; - struct fastrpc_buf *pbuf = &ctx->obuf, *obufs = 0; + struct fastrpc_buf *pbuf = &ctx->obuf, *obufs = NULL; struct smq_phy_page *pages; struct vm_area_struct *vma; struct ion_handle **handles = ctx->handles; void *args; remote_arg_t *pra = ctx->pra; remote_arg_t *rpra = ctx->rpra; - ssize_t rlen, used, size, copylen = 0; + size_t rlen, used, size, copylen = 0; uint32_t sc = ctx->sc, start; int i, inh, bufs = 0, err = 0, oix; int inbufs = REMOTE_SCALARS_INBUFS(sc); @@ -933,7 +976,7 @@ static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx, /* copy non ion buffers */ for (oix = 0; oix < inbufs + outbufs; ++oix) { int i = ctx->overps[oix]->raix; - ssize_t mlen = ctx->overps[oix]->mend - ctx->overps[oix]->mstart; + size_t mlen = ctx->overps[oix]->mend - ctx->overps[oix]->mstart; if (!pra[i].buf.len) continue; if (list[i].num) @@ -1093,7 +1136,7 @@ static int fastrpc_invoke_send(struct fastrpc_apps *me, msg.tid = current->pid; if (kernel) msg.pid = 0; - msg.invoke.header.ctx = ctx; + msg.invoke.header.ctx = ctx->ctxid; msg.invoke.header.handle = handle; msg.invoke.header.sc = sc; msg.invoke.page.addr = buf->phys; @@ -1123,16 +1166,35 @@ static void fastrpc_deinit(void) static void fastrpc_read_handler(int cid) { struct fastrpc_apps *me = &gfa; - struct smq_invoke_rsp rsp; - int ret = 0; + struct smq_invoke_rsp rsp = {0}; + int ret = 0, err = 0; + uint32_t index; do { ret = smd_read_from_cb(me->channel[cid].chan, &rsp, sizeof(rsp)); if (ret != sizeof(rsp)) break; - context_notify_user(rsp.ctx, rsp.retval); + index = (uint32_t)((rsp.ctx & FASTRPC_CTXID_MASK) >> 4); + VERIFY(err, index < FASTRPC_CTX_MAX); + if (err) + goto bail; + + VERIFY(err, !IS_ERR_OR_NULL(me->ctxtable[index])); + if (err) + goto bail; + + VERIFY(err, ((me->ctxtable[index]->ctxid == (rsp.ctx)) && + me->ctxtable[index]->magic == FASTRPC_CTX_MAGIC)); + if (err) + goto bail; + + context_notify_user(me->ctxtable[index], rsp.retval); } while (ret == sizeof(rsp)); + +bail: + if (err) + pr_err("adsprpc: invalid response or context\n"); } static void smd_event_handler(void *priv, unsigned event) @@ -1163,6 +1225,7 @@ static int fastrpc_init(void) spin_lock_init(&me->hlock); spin_lock_init(&me->wrlock); + spin_lock_init(&me->ctxlock); mutex_init(&me->smd_mutex); context_list_ctor(&me->clst); for (i = 0; i < RPC_HASH_SZ; ++i) @@ -1208,7 +1271,7 @@ static void free_dev(struct fastrpc_device *dev, struct file_data *fdata) static int alloc_dev(struct fastrpc_device **dev, struct file_data *fdata) { int err = 0; - struct fastrpc_device *fd = 0; + struct fastrpc_device *fd = NULL; VERIFY(err, 0 != try_module_get(THIS_MODULE)); if (err) @@ -1236,7 +1299,7 @@ static int get_dev(struct fastrpc_apps *me, struct file_data *fdata, struct fastrpc_device **rdev) { struct hlist_head *head; - struct fastrpc_device *dev = 0, *devfree = 0; + struct fastrpc_device *dev = NULL, *devfree = NULL; struct hlist_node *n; uint32_t h = hash_32(current->tgid, RPC_HASH_BITS); int err = 0; @@ -1282,7 +1345,7 @@ static int fastrpc_internal_invoke(struct fastrpc_apps *me, uint32_t mode, struct fastrpc_ioctl_invoke_fd *invokefd, struct file_data *fdata) { - struct smq_invoke_ctx *ctx = 0; + struct smq_invoke_ctx *ctx = NULL; struct fastrpc_ioctl_invoke *invoke = &invokefd->inv; int cid = fdata->cid; int interrupted = 0; @@ -1371,8 +1434,8 @@ static int fastrpc_init_process(struct file_data *fdata, { int err = 0; struct fastrpc_ioctl_invoke_fd ioctl; - struct smq_phy_page *pages = 0; - struct fastrpc_mmap *map = 0; + struct smq_phy_page *pages = NULL; + struct fastrpc_mmap *map = NULL; int npages = 0; struct fastrpc_apps *me = &gfa; if (init->flags == FASTRPC_INIT_ATTACH) { @@ -1400,6 +1463,12 @@ static int fastrpc_init_process(struct file_data *fdata, inbuf.pgid = current->tgid; inbuf.namelen = strlen(current->comm); inbuf.filelen = init->filelen; + if (!access_ok(0, (void const __user *)init->file, + init->filelen)) + goto bail; + if (!access_ok(1, (void const __user *)init->mem, + init->memlen)) + goto bail; VERIFY(err, 0 == map_buffer(me, fdata, init->memfd, (char *)init->mem, init->memlen, &map, &pages, &npages)); @@ -1521,7 +1590,7 @@ static int fastrpc_munmap_on_dsp(struct fastrpc_apps *me, struct { int pid; uintptr_t vaddrout; - ssize_t size; + size_t size; } inargs; inargs.pid = current->tgid; @@ -1575,9 +1644,9 @@ static int map_buffer(struct fastrpc_apps *me, struct file_data *fdata, struct smq_phy_page **ppages, int *pnpages) { struct ion_client *clnt = gfa.iclient; - struct ion_handle *handle = 0; - struct fastrpc_mmap *map = 0, *mapmatch = 0; - struct smq_phy_page *pages = 0; + struct ion_handle *handle = NULL; + struct fastrpc_mmap *map = NULL, *mapmatch = NULL; + struct smq_phy_page *pages = NULL; struct hlist_node *n; uintptr_t vaddrout = 0; int num; @@ -1654,8 +1723,8 @@ static int fastrpc_internal_mmap(struct fastrpc_apps *me, struct fastrpc_ioctl_mmap *mmap) { - struct fastrpc_mmap *map = 0; - struct smq_phy_page *pages = 0; + struct fastrpc_mmap *map = NULL; + struct smq_phy_page *pages = NULL; int num = 0; int err = 0; VERIFY(err, 0 == map_buffer(me, fdata, mmap->fd, (char *)mmap->vaddrin, @@ -1685,7 +1754,7 @@ static void cleanup_current_dev(struct file_data *fdata) struct fastrpc_device *dev, *devfree; rnext: - devfree = dev = 0; + devfree = dev = NULL; spin_lock(&me->hlock); head = &me->htbl[h]; hlist_for_each_entry_safe(dev, n, head, hn) { @@ -1723,9 +1792,9 @@ static int fastrpc_device_release(struct inode *inode, struct file *file) struct file_data *fdata = (struct file_data *)file->private_data; struct fastrpc_apps *me = &gfa; struct smq_context_list *clst = &me->clst; - struct smq_invoke_ctx *ictx = 0, *ctxfree; + struct smq_invoke_ctx *ictx = NULL, *ctxfree; struct hlist_node *n; - struct fastrpc_mmap *map = 0; + struct fastrpc_mmap *map = NULL; int cid = MINOR(inode->i_rdev); if (!fdata) @@ -1733,7 +1802,7 @@ static int fastrpc_device_release(struct inode *inode, struct file *file) (void)fastrpc_release_current_dsp_process(fdata); do { - ctxfree = 0; + ctxfree = NULL; spin_lock(&clst->hlock); hlist_for_each_entry_safe(ictx, n, &clst->interrupted, hn) { if ((ictx->tgid == current->tgid) && @@ -1771,7 +1840,7 @@ static int fastrpc_device_open(struct inode *inode, struct file *filp) mutex_lock(&me->smd_mutex); ssrcount = me->channel[cid].ssrcount; if ((kref_get_unless_zero(&me->channel[cid].kref) == 0) || - (me->channel[cid].chan == 0)) { + (me->channel[cid].chan == NULL)) { VERIFY(err, 0 == smd_named_open_on_edge( FASTRPC_SMD_GUID, gcinfo[cid].channel, @@ -1791,9 +1860,9 @@ static int fastrpc_device_open(struct inode *inode, struct file *filp) } mutex_unlock(&me->smd_mutex); - filp->private_data = 0; + filp->private_data = NULL; if (0 != try_module_get(THIS_MODULE)) { - struct file_data *fdata = 0; + struct file_data *fdata = NULL; /* This call will cause a dev to be created * which will addref this module */ @@ -1823,7 +1892,7 @@ static int fastrpc_device_open(struct inode *inode, struct file *filp) completion_bail: smd_close(me->channel[cid].chan); - me->channel[cid].chan = 0; + me->channel[cid].chan = NULL; smd_bail: mutex_unlock(&me->smd_mutex); return err; @@ -1845,7 +1914,7 @@ static long fastrpc_device_ioctl(struct file *file, unsigned int ioctl_num, switch (ioctl_num) { case FASTRPC_IOCTL_INVOKE_FD: case FASTRPC_IOCTL_INVOKE: - invokefd.fds = 0; + invokefd.fds = NULL; size = (ioctl_num == FASTRPC_IOCTL_INVOKE) ? sizeof(invokefd.inv) : sizeof(invokefd); VERIFY(err, 0 == copy_from_user(&invokefd, param, size)); @@ -1923,7 +1992,7 @@ static int fastrpc_restart_notifier_cb(struct notifier_block *nb, ctx->ssrcount++; if (ctx->chan) { smd_close(ctx->chan); - ctx->chan = 0; + ctx->chan = NULL; pr_info("'closed /dev/%s c %d %d'\n", gcinfo[cid].name, MAJOR(me->dev_no), cid); } diff --git a/drivers/char/adsprpc_compat.c b/drivers/char/adsprpc_compat.c index ee324dcf91f0e..160ad1851c9a8 100644 --- a/drivers/char/adsprpc_compat.c +++ b/drivers/char/adsprpc_compat.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 The Linux Foundation. All rights reserved. + * Copyright (c) 2014,2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -33,7 +33,7 @@ struct compat_remote_buf { compat_uptr_t pv; /* buffer pointer */ - compat_ssize_t len; /* length of buffer */ + compat_size_t len; /* length of buffer */ }; union compat_remote_arg { @@ -56,13 +56,13 @@ struct compat_fastrpc_ioctl_mmap { compat_int_t fd; /* ion fd */ compat_uint_t flags; /* flags for dsp to map with */ compat_uptr_t vaddrin; /* optional virtual address */ - compat_ssize_t size; /* size */ + compat_size_t size; /* size */ compat_uptr_t vaddrout; /* dsps virtual address */ }; struct compat_fastrpc_ioctl_munmap { compat_uptr_t vaddrout; /* address to unmap */ - compat_ssize_t size; /* size */ + compat_size_t size; /* size */ }; struct compat_fastrpc_ioctl_init { @@ -81,7 +81,7 @@ static int compat_get_fastrpc_ioctl_invoke( unsigned int cmd) { compat_uint_t u, sc; - compat_ssize_t s; + compat_size_t s; compat_uptr_t p; struct fastrpc_ioctl_invoke_fd *inv; union compat_remote_arg *pra32; @@ -164,7 +164,7 @@ static int compat_get_fastrpc_ioctl_mmap( { compat_uint_t u; compat_int_t i; - compat_ssize_t s; + compat_size_t s; compat_uptr_t p; int err; @@ -198,7 +198,7 @@ static int compat_get_fastrpc_ioctl_munmap( struct fastrpc_ioctl_munmap __user *unmap) { compat_uptr_t p; - compat_ssize_t s; + compat_size_t s; int err; err = get_user(p, &unmap32->vaddrout); diff --git a/drivers/char/adsprpc_shared.h b/drivers/char/adsprpc_shared.h index c0f8d3cc8c4e3..b8e5f23a56c04 100644 --- a/drivers/char/adsprpc_shared.h +++ b/drivers/char/adsprpc_shared.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2014,2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -93,7 +93,7 @@ do {\ struct remote_buf { void *pv; /* buffer pointer */ - ssize_t len; /* length of buffer */ + size_t len; /* length of buffer */ }; union remote_arg { @@ -114,37 +114,37 @@ struct fastrpc_ioctl_invoke_fd { struct fastrpc_ioctl_init { uint32_t flags; /* one of FASTRPC_INIT_* macros */ - uintptr_t __user file; /* pointer to elf file */ - int32_t filelen; /* elf file length */ + uintptr_t file; /* pointer to elf file */ + uint32_t filelen; /* elf file length */ int32_t filefd; /* ION fd for the file */ - uintptr_t __user mem; /* mem for the PD */ - int32_t memlen; /* mem length */ + uintptr_t mem; /* mem for the PD */ + uint32_t memlen; /* mem length */ int32_t memfd; /* ION fd for the mem */ }; struct fastrpc_ioctl_munmap { uintptr_t vaddrout; /* address to unmap */ - ssize_t size; /* size */ + size_t size; /* size */ }; struct fastrpc_ioctl_mmap { int fd; /* ion fd */ uint32_t flags; /* flags for dsp to map with */ - uintptr_t __user *vaddrin; /* optional virtual address */ - ssize_t size; /* size */ + uintptr_t vaddrin; /* optional virtual address */ + size_t size; /* size */ uintptr_t vaddrout; /* dsps virtual address */ }; struct smq_null_invoke { - struct smq_invoke_ctx *ctx; /* invoke caller context */ + uint64_t ctx; /* invoke caller context */ uint32_t handle; /* handle to invoke */ uint32_t sc; /* scalars structure describing the data */ }; struct smq_phy_page { unsigned long addr; /* physical address */ - ssize_t size; /* size of contiguous region */ + size_t size; /* size of contiguous region */ }; struct smq_invoke_buf { @@ -164,21 +164,22 @@ struct smq_msg { }; struct smq_invoke_rsp { - struct smq_invoke_ctx *ctx; /* invoke caller context */ + uint64_t ctx; /* invoke caller context */ int retval; /* invoke return value */ }; static inline struct smq_invoke_buf *smq_invoke_buf_start(remote_arg_t *pra, uint32_t sc) { - int len = REMOTE_SCALARS_LENGTH(sc); + unsigned int len = REMOTE_SCALARS_LENGTH(sc); + return (struct smq_invoke_buf *)(&pra[len]); } static inline struct smq_phy_page *smq_phy_page_start(uint32_t sc, struct smq_invoke_buf *buf) { - int nTotal = REMOTE_SCALARS_INBUFS(sc) + REMOTE_SCALARS_OUTBUFS(sc); + uint32_t nTotal = REMOTE_SCALARS_INBUFS(sc)+REMOTE_SCALARS_OUTBUFS(sc); return (struct smq_phy_page *)(&buf[nTotal]); } diff --git a/drivers/char/diag/diag_dci.c b/drivers/char/diag/diag_dci.c index f954c0c00d2d9..444e79849799a 100644 --- a/drivers/char/diag/diag_dci.c +++ b/drivers/char/diag/diag_dci.c @@ -798,11 +798,11 @@ static void dci_process_ctrl_status(unsigned char *buf, int len, int token) { struct diag_ctrl_dci_status *header = NULL; unsigned char *temp = buf; - uint32_t read_len = 0; + unsigned int read_len = 0; uint8_t i; int peripheral_mask, status; - if (!buf || (len < sizeof(struct diag_ctrl_dci_status))) { + if (!buf || len < 2 || (len < sizeof(struct diag_ctrl_dci_status))) { pr_err("diag: In %s, invalid buf %pK or length: %d\n", __func__, buf, len); return; @@ -818,7 +818,7 @@ static void dci_process_ctrl_status(unsigned char *buf, int len, int token) read_len += sizeof(struct diag_ctrl_dci_status); for (i = 0; i < header->count; i++) { - if (read_len > len) { + if (read_len > (len - 2)) { pr_err("diag: In %s, Invalid length len: %d\n", __func__, len); return; @@ -860,7 +860,9 @@ static void dci_process_ctrl_handshake_pkt(unsigned char *buf, int len, unsigned char *temp = buf; int err = 0; - if (!buf || (len < sizeof(struct diag_ctrl_dci_handshake_pkt))) + if (!buf) + return; + if (len < 0 || len < sizeof(struct diag_ctrl_dci_handshake_pkt)) return; if (!VALID_DCI_TOKEN(token)) @@ -1097,18 +1099,31 @@ void extract_dci_events(unsigned char *buf, int len, int data_source, int token) struct list_head *start, *temp; struct diag_dci_client_tbl *entry = NULL; - length = *(uint16_t *)(buf + 1); /* total length of event series */ - if (length == 0) { - pr_err("diag: Incoming dci event length is invalid\n"); + if (!buf) { + pr_err("diag: In %s buffer is NULL\n", __func__); return; } - /* Move directly to the start of the event series. 1 byte for - * event code and 2 bytes for the length field. - */ - /* The length field indicates the total length removing the cmd_code + /* + * 1 byte for event code and 2 bytes for the length field. + * The length field indicates the total length removing the cmd_code * and the lenght field. The event parsing in that case should happen * till the end. */ + if (len < 3) { + pr_err("diag: In %s invalid len: %d\n", __func__, len); + return; + } + length = *(uint16_t *)(buf + 1); /* total length of event series */ + if ((length == 0) || (len != (length + 3))) { + pr_err("diag: Incoming dci event length: %d is invalid\n", + length); + return; + } + /* + * Move directly to the start of the event series. + * The event parsing should happen from start of event + * series till the end. + */ temp_len = 3; while (temp_len < length) { event_id_packet = *(uint16_t *)(buf + temp_len); @@ -1125,30 +1140,60 @@ void extract_dci_events(unsigned char *buf, int len, int data_source, int token) * necessary. */ timestamp_len = 8; - memcpy(timestamp, buf + temp_len + 2, timestamp_len); + if ((temp_len + timestamp_len + 2) <= len) + memcpy(timestamp, buf + temp_len + 2, + timestamp_len); + else { + pr_err("diag: Invalid length in %s, len: %d, temp_len: %d", + __func__, len, temp_len); + return; + } } /* 13th and 14th bit represent the payload length */ if (((event_id_packet & 0x6000) >> 13) == 3) { payload_len_field = 1; - payload_len = *(uint8_t *) + if ((temp_len + timestamp_len + 3) <= len) { + payload_len = *(uint8_t *) (buf + temp_len + 2 + timestamp_len); - if (payload_len < (MAX_EVENT_SIZE - 13)) { - /* copy the payload length and the payload */ + } else { + pr_err("diag: Invalid length in %s, len: %d, temp_len: %d", + __func__, len, temp_len); + return; + } + if ((payload_len < (MAX_EVENT_SIZE - 13)) && + ((temp_len + timestamp_len + payload_len + 3) <= len)) { + /* + * Copy the payload length and the payload + * after skipping temp_len bytes for already + * parsed packet, timestamp_len for timestamp + * buffer, 2 bytes for event_id_packet. + */ memcpy(event_data + 12, buf + temp_len + 2 + timestamp_len, 1); memcpy(event_data + 13, buf + temp_len + 2 + timestamp_len + 1, payload_len); } else { - pr_err("diag: event > %d, payload_len = %d\n", - (MAX_EVENT_SIZE - 13), payload_len); + pr_err("diag: event > %d, payload_len = %d, temp_len = %d\n", + (MAX_EVENT_SIZE - 13), payload_len, temp_len); return; } } else { payload_len_field = 0; payload_len = (event_id_packet & 0x6000) >> 13; - /* copy the payload */ - memcpy(event_data + 12, buf + temp_len + 2 + + /* + * Copy the payload after skipping temp_len bytes + * for already parsed packet, timestamp_len for + * timestamp buffer, 2 bytes for event_id_packet. + */ + if ((payload_len < (MAX_EVENT_SIZE - 12)) && + ((temp_len + timestamp_len + payload_len + 2) <= len)) + memcpy(event_data + 12, buf + temp_len + 2 + timestamp_len, payload_len); + else { + pr_err("diag: event > %d, payload_len = %d, temp_len = %d\n", + (MAX_EVENT_SIZE - 12), payload_len, temp_len); + return; + } } /* Before copying the data to userspace, check if we are still @@ -1264,19 +1309,19 @@ void extract_dci_log(unsigned char *buf, int len, int data_source, int token) pr_err("diag: In %s buffer is NULL\n", __func__); return; } - - /* The first six bytes for the incoming log packet contains - * Command code (2), the length of the packet (2) and the length - * of the log (2) + /* + * The first eight bytes for the incoming log packet contains + * Command code (2), the length of the packet (2), the length + * of the log (2) and log code (2) */ - log_code = *(uint16_t *)(buf + 6); - read_bytes += sizeof(uint16_t) + 6; - if (read_bytes > len) { - pr_err("diag: Invalid length in %s, len: %d, read: %d", - __func__, len, read_bytes); + if (len < 8) { + pr_err("diag: In %s invalid len: %d\n", __func__, len); return; } + log_code = *(uint16_t *)(buf + 6); + read_bytes += sizeof(uint16_t) + 6; + /* parse through log mask table of each client and check mask */ list_for_each_safe(start, temp, &driver->dci_client_list) { entry = list_entry(start, struct diag_dci_client_tbl, track); diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index cdff0721fe1a5..cb9f136d730a1 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2183,10 +2183,8 @@ int cpufreq_update_policy(unsigned int cpu) struct cpufreq_policy new_policy; int ret; - if (!policy) { - ret = -ENODEV; - goto no_policy; - } + if (!policy) + return -ENODEV; down_write(&policy->rwsem); @@ -2201,8 +2199,13 @@ int cpufreq_update_policy(unsigned int cpu) * BIOS might change freq behind our back * -> ask driver for current freq and notify governors about a change */ - if (cpufreq_driver->get) { + if (cpufreq_driver->get && !cpufreq_driver->setpolicy) { new_policy.cur = cpufreq_driver->get(cpu); + if (WARN_ON(!new_policy.cur)) { + ret = -EIO; + goto unlock; + } + if (!policy->cur) { pr_debug("Driver did not initialize current freq"); policy->cur = new_policy.cur; @@ -2215,10 +2218,10 @@ int cpufreq_update_policy(unsigned int cpu) ret = cpufreq_set_policy(policy, &new_policy); +unlock: up_write(&policy->rwsem); cpufreq_cpu_put(policy); -no_policy: return ret; } EXPORT_SYMBOL(cpufreq_update_policy); diff --git a/drivers/cpufreq/cpufreq_interactive.c b/drivers/cpufreq/cpufreq_interactive.c index ba888b776d775..a9ec016a57379 100644 --- a/drivers/cpufreq/cpufreq_interactive.c +++ b/drivers/cpufreq/cpufreq_interactive.c @@ -435,6 +435,8 @@ static void cpufreq_interactive_timer(unsigned long data) return; if (!ppol->governor_enabled) goto exit; + if (ppol->policy->min == ppol->policy->max) + goto rearm; fcpu = cpumask_first(ppol->policy->related_cpus); now = ktime_to_us(ktime_get()); diff --git a/drivers/cpufreq/qcom-cpufreq.c b/drivers/cpufreq/qcom-cpufreq.c index 00c7bcbffd7f3..8231c159cb086 100644 --- a/drivers/cpufreq/qcom-cpufreq.c +++ b/drivers/cpufreq/qcom-cpufreq.c @@ -42,7 +42,7 @@ struct cpufreq_suspend_t { int device_suspended; }; -static DEFINE_PER_CPU(struct cpufreq_suspend_t, cpufreq_suspend); +static DEFINE_PER_CPU(struct cpufreq_suspend_t, suspend_data); static int set_cpu_freq(struct cpufreq_policy *policy, unsigned int new_freq, unsigned int index) @@ -78,12 +78,12 @@ static int msm_cpufreq_target(struct cpufreq_policy *policy, int index; struct cpufreq_frequency_table *table; - mutex_lock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex); + mutex_lock(&per_cpu(suspend_data, policy->cpu).suspend_mutex); if (target_freq == policy->cur) goto done; - if (per_cpu(cpufreq_suspend, policy->cpu).device_suspended) { + if (per_cpu(suspend_data, policy->cpu).device_suspended) { pr_debug("cpufreq: cpu%d scheduling frequency change " "in suspend.\n", policy->cpu); ret = -EFAULT; @@ -111,7 +111,7 @@ static int msm_cpufreq_target(struct cpufreq_policy *policy, ret = set_cpu_freq(policy, table[index].frequency, table[index].driver_data); done: - mutex_unlock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex); + mutex_unlock(&per_cpu(suspend_data, policy->cpu).suspend_mutex); return ret; } @@ -241,9 +241,9 @@ static int msm_cpufreq_suspend(void) int cpu; for_each_possible_cpu(cpu) { - mutex_lock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex); - per_cpu(cpufreq_suspend, cpu).device_suspended = 1; - mutex_unlock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex); + mutex_lock(&per_cpu(suspend_data, cpu).suspend_mutex); + per_cpu(suspend_data, cpu).device_suspended = 1; + mutex_unlock(&per_cpu(suspend_data, cpu).suspend_mutex); } return NOTIFY_DONE; @@ -255,7 +255,7 @@ static int msm_cpufreq_resume(void) struct cpufreq_policy policy; for_each_possible_cpu(cpu) { - per_cpu(cpufreq_suspend, cpu).device_suspended = 0; + per_cpu(suspend_data, cpu).device_suspended = 0; } /* @@ -476,8 +476,8 @@ static int __init msm_cpufreq_register(void) int cpu, rc; for_each_possible_cpu(cpu) { - mutex_init(&(per_cpu(cpufreq_suspend, cpu).suspend_mutex)); - per_cpu(cpufreq_suspend, cpu).device_suspended = 0; + mutex_init(&(per_cpu(suspend_data, cpu).suspend_mutex)); + per_cpu(suspend_data, cpu).device_suspended = 0; } rc = platform_driver_probe(&msm_cpufreq_plat_driver, @@ -486,7 +486,7 @@ static int __init msm_cpufreq_register(void) /* Unblock hotplug if msm-cpufreq probe fails */ unregister_hotcpu_notifier(&msm_cpufreq_cpu_notifier); for_each_possible_cpu(cpu) - mutex_destroy(&(per_cpu(cpufreq_suspend, cpu). + mutex_destroy(&(per_cpu(suspend_data, cpu). suspend_mutex)); return rc; } diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index 155a1d3518934..2eceafe8a2c40 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -77,11 +77,23 @@ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, int entered_state; struct cpuidle_state *target_state = &drv->states[index]; + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); entered_state = target_state->enter(dev, drv, index); + time_end = ktime_get(); + local_irq_enable(); + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + if (entered_state >= 0) { /* Update cpuidle counters */ /* This can be moved to within driver enter routine @@ -169,7 +181,6 @@ void cpuidle_install_idle_handler(void) /* Make sure all changes finished before we switch to new idle */ smp_wmb(); initialized = 1; - kick_all_cpus_sync(); } } @@ -458,7 +469,7 @@ void cpuidle_unregister(struct cpuidle_driver *drv) int cpu; struct cpuidle_device *device; - for_each_cpu(cpu, drv->cpumask) { + for_each_possible_cpu(cpu) { device = &per_cpu(cpuidle_dev, cpu); cpuidle_unregister_device(device); } @@ -490,7 +501,7 @@ int cpuidle_register(struct cpuidle_driver *drv, return ret; } - for_each_cpu(cpu, drv->cpumask) { + for_each_possible_cpu(cpu) { device = &per_cpu(cpuidle_dev, cpu); device->cpu = cpu; diff --git a/drivers/cpuidle/driver.c b/drivers/cpuidle/driver.c index 95f3dc6747321..2253271b3fac3 100644 --- a/drivers/cpuidle/driver.c +++ b/drivers/cpuidle/driver.c @@ -18,140 +18,167 @@ DEFINE_SPINLOCK(cpuidle_driver_lock); -#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS - -static DEFINE_PER_CPU(struct cpuidle_driver *, cpuidle_drivers); +static void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu); +static struct cpuidle_driver * __cpuidle_get_cpu_driver(int cpu); -static struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu) +static void cpuidle_setup_broadcast_timer(void *arg) { - return per_cpu(cpuidle_drivers, cpu); + int cpu = smp_processor_id(); + clockevents_notify((long)(arg), &cpu); } -static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv) +static void __cpuidle_driver_init(struct cpuidle_driver *drv, int cpu) { - int cpu; + int i; + + drv->refcnt = 0; - for_each_cpu(cpu, drv->cpumask) { + for (i = drv->state_count - 1; i >= 0 ; i--) { - if (drv != __cpuidle_get_cpu_driver(cpu)) + if (!(drv->states[i].flags & CPUIDLE_FLAG_TIMER_STOP)) continue; - per_cpu(cpuidle_drivers, cpu) = NULL; + drv->bctimer = 1; + on_each_cpu_mask(get_cpu_mask(cpu), cpuidle_setup_broadcast_timer, + (void *)CLOCK_EVT_NOTIFY_BROADCAST_ON, 1); + break; } } -static inline int __cpuidle_set_driver(struct cpuidle_driver *drv) +static int __cpuidle_register_driver(struct cpuidle_driver *drv, int cpu) { - int cpu; + if (!drv || !drv->state_count) + return -EINVAL; - for_each_cpu(cpu, drv->cpumask) { + if (cpuidle_disabled()) + return -ENODEV; - if (__cpuidle_get_cpu_driver(cpu)) { - __cpuidle_unset_driver(drv); - return -EBUSY; - } + if (__cpuidle_get_cpu_driver(cpu)) + return -EBUSY; - per_cpu(cpuidle_drivers, cpu) = drv; - } + __cpuidle_driver_init(drv, cpu); + + __cpuidle_set_cpu_driver(drv, cpu); return 0; } -#else +static void __cpuidle_unregister_driver(struct cpuidle_driver *drv, int cpu) +{ + if (drv != __cpuidle_get_cpu_driver(cpu)) + return; -static struct cpuidle_driver *cpuidle_curr_driver; + if (!WARN_ON(drv->refcnt > 0)) + __cpuidle_set_cpu_driver(NULL, cpu); -static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu) -{ - return cpuidle_curr_driver; + if (drv->bctimer) { + drv->bctimer = 0; + on_each_cpu_mask(get_cpu_mask(cpu), cpuidle_setup_broadcast_timer, + (void *)CLOCK_EVT_NOTIFY_BROADCAST_OFF, 1); + } } -static inline int __cpuidle_set_driver(struct cpuidle_driver *drv) -{ - if (cpuidle_curr_driver) - return -EBUSY; +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS - cpuidle_curr_driver = drv; +static DEFINE_PER_CPU(struct cpuidle_driver *, cpuidle_drivers); - return 0; +static void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu) +{ + per_cpu(cpuidle_drivers, cpu) = drv; } -static inline void __cpuidle_unset_driver(struct cpuidle_driver *drv) +static struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu) { - if (drv == cpuidle_curr_driver) - cpuidle_curr_driver = NULL; + return per_cpu(cpuidle_drivers, cpu); } -#endif - -static void cpuidle_setup_broadcast_timer(void *arg) +static void __cpuidle_unregister_all_cpu_driver(struct cpuidle_driver *drv) { - int cpu = smp_processor_id(); - clockevents_notify((long)(arg), &cpu); + int cpu; + for_each_present_cpu(cpu) + __cpuidle_unregister_driver(drv, cpu); } -static int __cpuidle_driver_init(struct cpuidle_driver *drv) +static int __cpuidle_register_all_cpu_driver(struct cpuidle_driver *drv) { - int i; + int ret = 0; + int i, cpu; - drv->refcnt = 0; - - if (!drv->cpumask) - drv->cpumask = (struct cpumask *)cpu_possible_mask; - - for (i = drv->state_count - 1; i >= 0 ; i--) { + for_each_present_cpu(cpu) { + ret = __cpuidle_register_driver(drv, cpu); + if (ret) + break; + } - if (!(drv->states[i].flags & CPUIDLE_FLAG_TIMER_STOP)) - continue; + if (ret) + for_each_present_cpu(i) { + if (i == cpu) + break; + __cpuidle_unregister_driver(drv, i); + } - drv->bctimer = 1; - break; - } - return 0; + return ret; } -static int __cpuidle_register_driver(struct cpuidle_driver *drv) +int cpuidle_register_cpu_driver(struct cpuidle_driver *drv, int cpu) { int ret; - if (!drv || !drv->state_count) - return -EINVAL; + spin_lock(&cpuidle_driver_lock); + ret = __cpuidle_register_driver(drv, cpu); + spin_unlock(&cpuidle_driver_lock); - if (cpuidle_disabled()) - return -ENODEV; + return ret; +} - ret = __cpuidle_driver_init(drv); - if (ret) - return ret; +void cpuidle_unregister_cpu_driver(struct cpuidle_driver *drv, int cpu) +{ + spin_lock(&cpuidle_driver_lock); + __cpuidle_unregister_driver(drv, cpu); + spin_unlock(&cpuidle_driver_lock); +} - ret = __cpuidle_set_driver(drv); - if (ret) - return ret; +/** + * cpuidle_register_driver - registers a driver + * @drv: the driver + */ +int cpuidle_register_driver(struct cpuidle_driver *drv) +{ + int ret; - if (drv->bctimer) - on_each_cpu_mask(drv->cpumask, cpuidle_setup_broadcast_timer, - (void *)CLOCK_EVT_NOTIFY_BROADCAST_ON, 1); + spin_lock(&cpuidle_driver_lock); + ret = __cpuidle_register_all_cpu_driver(drv); + spin_unlock(&cpuidle_driver_lock); - return 0; + return ret; } +EXPORT_SYMBOL_GPL(cpuidle_register_driver); /** * cpuidle_unregister_driver - unregisters a driver * @drv: the driver */ -static void __cpuidle_unregister_driver(struct cpuidle_driver *drv) +void cpuidle_unregister_driver(struct cpuidle_driver *drv) { - if (WARN_ON(drv->refcnt > 0)) - return; + spin_lock(&cpuidle_driver_lock); + __cpuidle_unregister_all_cpu_driver(drv); + spin_unlock(&cpuidle_driver_lock); +} +EXPORT_SYMBOL_GPL(cpuidle_unregister_driver); - if (drv->bctimer) { - drv->bctimer = 0; - on_each_cpu_mask(drv->cpumask, cpuidle_setup_broadcast_timer, - (void *)CLOCK_EVT_NOTIFY_BROADCAST_OFF, 1); - } +#else + +static struct cpuidle_driver *cpuidle_curr_driver; - __cpuidle_unset_driver(drv); +static inline void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu) +{ + cpuidle_curr_driver = drv; +} + +static inline struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu) +{ + return cpuidle_curr_driver; } /** @@ -160,11 +187,13 @@ static void __cpuidle_unregister_driver(struct cpuidle_driver *drv) */ int cpuidle_register_driver(struct cpuidle_driver *drv) { - int ret; + int ret, cpu; + cpu = get_cpu(); spin_lock(&cpuidle_driver_lock); - ret = __cpuidle_register_driver(drv); + ret = __cpuidle_register_driver(drv, cpu); spin_unlock(&cpuidle_driver_lock); + put_cpu(); return ret; } @@ -176,11 +205,16 @@ EXPORT_SYMBOL_GPL(cpuidle_register_driver); */ void cpuidle_unregister_driver(struct cpuidle_driver *drv) { + int cpu; + + cpu = get_cpu(); spin_lock(&cpuidle_driver_lock); - __cpuidle_unregister_driver(drv); + __cpuidle_unregister_driver(drv, cpu); spin_unlock(&cpuidle_driver_lock); + put_cpu(); } EXPORT_SYMBOL_GPL(cpuidle_unregister_driver); +#endif /** * cpuidle_get_driver - return the current driver diff --git a/drivers/cpuidle/lpm-levels-of.c b/drivers/cpuidle/lpm-levels-of.c index bedef3098441d..b6979cd744cf2 100644 --- a/drivers/cpuidle/lpm-levels-of.c +++ b/drivers/cpuidle/lpm-levels-of.c @@ -37,87 +37,25 @@ static const struct lpm_type_str lpm_types[] = { {SUSPEND, "suspend_enabled"}, }; -static DEFINE_PER_CPU(uint32_t *, max_residency); static struct lpm_level_avail *cpu_level_available[NR_CPUS]; static struct platform_device *lpm_pdev; -static void *get_enabled_ptr(struct kobj_attribute *attr, - struct lpm_level_avail *avail) +static void *get_avail_val(struct kobject *kobj, struct kobj_attribute *attr) { void *arg = NULL; - - if (!strcmp(attr->attr.name, lpm_types[IDLE].str)) - arg = (void *) &avail->idle_enabled; - else if (!strcmp(attr->attr.name, lpm_types[SUSPEND].str)) - arg = (void *) &avail->suspend_enabled; - - return arg; -} - -static struct lpm_level_avail *get_avail_ptr(struct kobject *kobj, - struct kobj_attribute *attr) -{ struct lpm_level_avail *avail = NULL; - if (!strcmp(attr->attr.name, lpm_types[IDLE].str)) + if (!strcmp(attr->attr.name, lpm_types[IDLE].str)) { avail = container_of(attr, struct lpm_level_avail, idle_enabled_attr); - else if (!strcmp(attr->attr.name, lpm_types[SUSPEND].str)) + arg = (void *) &avail->idle_enabled; + } else if (!strcmp(attr->attr.name, lpm_types[SUSPEND].str)) { avail = container_of(attr, struct lpm_level_avail, suspend_enabled_attr); - - return avail; -} - -static void set_optimum_cpu_residency(struct lpm_cpu *cpu, int cpu_id, - bool probe_time) -{ - int i, j; - bool mode_avail; - uint32_t *residency = per_cpu(max_residency, cpu_id); - - for (i = 0; i < cpu->nlevels; i++) { - struct power_params *pwr = &cpu->levels[i].pwr; - - residency[i] = ~0; - for (j = i + 1; j < cpu->nlevels; j++) { - mode_avail = probe_time || - lpm_cpu_mode_allow(cpu_id, j, true); - - if (mode_avail && - (residency[i] > pwr->residencies[j]) && - (pwr->residencies[j] != 0)) - residency[i] = pwr->residencies[j]; - } - } -} - -static void set_optimum_cluster_residency(struct lpm_cluster *cluster, - bool probe_time) -{ - int i, j; - bool mode_avail = false; - - for (i = 0; i < cluster->nlevels; i++) { - struct power_params *pwr = &cluster->levels[i].pwr; - - pwr->max_residency = ~0; - for (j = 0; j < cluster->nlevels; j++) { - if (i >= j) - mode_avail = probe_time || - lpm_cluster_mode_allow(cluster, i, - true); - if (mode_avail && - (pwr->max_residency > pwr->residencies[j]) && - (pwr->residencies[j] != 0)) - pwr->max_residency = pwr->residencies[j]; - } + arg = (void *) &avail->suspend_enabled; } -} -uint32_t *get_per_cpu_max_residency(int cpu) -{ - return per_cpu(max_residency, cpu); + return arg; } ssize_t lpm_enable_show(struct kobject *kobj, struct kobj_attribute *attr, @@ -126,7 +64,7 @@ ssize_t lpm_enable_show(struct kobject *kobj, struct kobj_attribute *attr, int ret = 0; struct kernel_param kp; - kp.arg = get_enabled_ptr(attr, get_avail_ptr(kobj, attr)); + kp.arg = get_avail_val(kobj, attr); ret = param_get_bool(buf, &kp); if (ret > 0) { strlcat(buf, "\n", PAGE_SIZE); @@ -141,23 +79,15 @@ ssize_t lpm_enable_store(struct kobject *kobj, struct kobj_attribute *attr, { int ret = 0; struct kernel_param kp; - struct lpm_level_avail *avail; - avail = get_avail_ptr(kobj, attr); - kp.arg = get_enabled_ptr(attr, avail); + kp.arg = get_avail_val(kobj, attr); ret = param_set_bool(buf, &kp); - if (avail->cpu_node) - set_optimum_cpu_residency(avail->data, avail->idx, false); - else - set_optimum_cluster_residency(avail->data, false); - return ret ? ret : len; } static int create_lvl_avail_nodes(const char *name, - struct kobject *parent, struct lpm_level_avail *avail, - void *data, int index, bool cpu_node) + struct kobject *parent, struct lpm_level_avail *avail) { struct attribute_group *attr_group = NULL; struct attribute **attr = NULL; @@ -182,13 +112,11 @@ static int create_lvl_avail_nodes(const char *name, goto failed; } - sysfs_attr_init(&avail->idle_enabled_attr.attr); avail->idle_enabled_attr.attr.name = lpm_types[IDLE].str; avail->idle_enabled_attr.attr.mode = 0644; avail->idle_enabled_attr.show = lpm_enable_show; avail->idle_enabled_attr.store = lpm_enable_store; - sysfs_attr_init(&avail->suspend_enabled_attr.attr); avail->suspend_enabled_attr.attr.name = lpm_types[SUSPEND].str; avail->suspend_enabled_attr.attr.mode = 0644; avail->suspend_enabled_attr.show = lpm_enable_show; @@ -208,9 +136,6 @@ static int create_lvl_avail_nodes(const char *name, avail->idle_enabled = true; avail->suspend_enabled = true; avail->kobj = kobj; - avail->data = data; - avail->idx = index; - avail->cpu_node = cpu_node; return ret; @@ -262,8 +187,7 @@ static int create_cpu_lvl_nodes(struct lpm_cluster *p, struct kobject *parent) } ret = create_lvl_avail_nodes(p->cpu->levels[j].name, - cpu_kobj[cpu_idx], &level_list[i], - (void *)p->cpu, cpu, true); + cpu_kobj[cpu_idx], &level_list[i]); if (ret) goto release_kobj; } @@ -298,8 +222,7 @@ int create_cluster_lvl_nodes(struct lpm_cluster *p, struct kobject *kobj) for (i = 0; i < p->nlevels; i++) { ret = create_lvl_avail_nodes(p->levels[i].level_name, - cluster_kobj, &p->levels[i].available, - (void *)p, 0, false); + cluster_kobj, &p->levels[i].available); if (ret) return ret; } @@ -354,9 +277,7 @@ static int parse_cluster_params(struct device_node *node, struct lpm_cluster *c) }; struct lpm_match match_tbl[] = { {"l2", set_l2_mode}, - {"cci", set_system_mode}, - {"l3", set_l3_mode}, - {"cbf", set_system_mode}, + {"cci", set_cci_mode}, }; key = "label"; @@ -468,9 +389,6 @@ static int parse_power_params(struct device_node *node, key = "qcom,time-overhead"; ret = of_property_read_u32(node, key, &pwr->time_overhead_us); - if (ret) - goto fail; - fail: if (ret) pr_err("%s(): %s Error reading %s\n", __func__, node->name, @@ -523,8 +441,6 @@ static int parse_cluster_level(struct device_node *node, } level->notify_rpm = of_property_read_bool(node, "qcom,notify-rpm"); - level->disable_dynamic_routing = of_property_read_bool(node, - "qcom,disable-dynamic-int-routing"); level->last_core_only = of_property_read_bool(node, "qcom,last-core-only"); @@ -606,31 +522,11 @@ static int get_cpumask_for_node(struct device_node *node, struct cpumask *mask) return -EINVAL; } -static int calculate_residency(struct power_params *base_pwr, - struct power_params *next_pwr) -{ - int32_t residency = (int32_t)(next_pwr->energy_overhead - - base_pwr->energy_overhead) - - ((int32_t)(next_pwr->ss_power * next_pwr->time_overhead_us) - - (int32_t)(base_pwr->ss_power * base_pwr->time_overhead_us)); - - residency /= (int32_t)(base_pwr->ss_power - next_pwr->ss_power); - - if (residency < 0) { - __WARN_printf("%s: Incorrect power attributes for LPM\n", - __func__); - return next_pwr->time_overhead_us; - } - - return residency < next_pwr->time_overhead_us ? - next_pwr->time_overhead_us : residency; -} - static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c) { struct device_node *n; int ret = -ENOMEM; - int i, j; + int i; c->cpu = devm_kzalloc(&lpm_pdev->dev, sizeof(*c->cpu), GFP_KERNEL); if (!c->cpu) @@ -664,22 +560,6 @@ static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c) l->use_bc_timer = of_property_read_bool(n, key); } - for (i = 0; i < c->cpu->nlevels; i++) { - for (j = 0; j < c->cpu->nlevels; j++) { - if (i >= j) { - c->cpu->levels[i].pwr.residencies[j] = 0; - continue; - } - - c->cpu->levels[i].pwr.residencies[j] = - calculate_residency(&c->cpu->levels[i].pwr, - &c->cpu->levels[j].pwr); - - pr_err("%s: idx %d %u\n", __func__, j, - c->cpu->levels[i].pwr.residencies[j]); - } - } - return 0; failed: for (i = 0; i < c->cpu->nlevels; i++) { @@ -694,14 +574,12 @@ static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c) void free_cluster_node(struct lpm_cluster *cluster) { - struct list_head *list; int i; + struct lpm_cluster *cl, *m; - list_for_each(list, &cluster->child) { - struct lpm_cluster *n; - n = list_entry(list, typeof(*n), list); - list_del(list); - free_cluster_node(n); + list_for_each_entry_safe(cl, m, &cluster->child, list) { + list_del(&cl->list); + free_cluster_node(cl); }; if (cluster->cpu) { @@ -736,7 +614,6 @@ struct lpm_cluster *parse_cluster(struct device_node *node, struct device_node *n; char *key; int ret = 0; - int i, j; c = devm_kzalloc(&lpm_pdev->dev, sizeof(*c), GFP_KERNEL); if (!c) @@ -794,32 +671,11 @@ struct lpm_cluster *parse_cluster(struct device_node *node, goto failed_parse_cluster; c->aff_level = 1; - - for_each_cpu(i, &c->child_cpus) { - per_cpu(max_residency, i) = devm_kzalloc( - &lpm_pdev->dev, - sizeof(uint32_t) * c->cpu->nlevels, - GFP_KERNEL); - if (!per_cpu(max_residency, i)) - return ERR_PTR(-ENOMEM); - set_optimum_cpu_residency(c->cpu, i, true); - } } } c->last_level = c->nlevels-1; - for (i = 0; i < c->nlevels; i++) { - for (j = 0; j < c->nlevels; j++) { - if (i >= j) { - c->levels[i].pwr.residencies[j] = 0; - continue; - } - c->levels[i].pwr.residencies[j] = calculate_residency( - &c->levels[i].pwr, &c->levels[j].pwr); - } - } - set_optimum_cluster_residency(c, true); return c; failed_parse_cluster: diff --git a/drivers/cpuidle/lpm-levels.c b/drivers/cpuidle/lpm-levels.c index ccf8acda592f8..e93b587102aba 100644 --- a/drivers/cpuidle/lpm-levels.c +++ b/drivers/cpuidle/lpm-levels.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -103,7 +102,6 @@ module_param_named( static int msm_pm_sleep_time_override; module_param_named(sleep_time_override, msm_pm_sleep_time_override, int, S_IRUGO | S_IWUSR | S_IWGRP); -static uint64_t suspend_wake_time; static bool print_parsed_dt; module_param_named( @@ -119,21 +117,6 @@ s32 msm_cpuidle_get_deep_idle_latency(void) return 10; } -void lpm_suspend_wake_time(uint64_t wakeup_time) -{ - if (wakeup_time <= 0) { - suspend_wake_time = msm_pm_sleep_time_override; - return; - } - - if (msm_pm_sleep_time_override && - (msm_pm_sleep_time_override < wakeup_time)) - suspend_wake_time = msm_pm_sleep_time_override; - else - suspend_wake_time = wakeup_time; -} -EXPORT_SYMBOL(lpm_suspend_wake_time); - static void update_debug_pc_event(enum debug_event event, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4) { @@ -208,40 +191,33 @@ int set_l2_mode(struct low_power_ops *ops, int mode, bool notify_rpm) { int lpm = mode; int rc = 0; - struct low_power_ops *cpu_ops = per_cpu(cpu_cluster, - smp_processor_id())->lpm_dev; - - if (cpu_ops->tz_flag & MSM_SCM_L2_OFF || - cpu_ops->tz_flag & MSM_SCM_L2_GDHS) + if (ops->tz_flag == MSM_SCM_L2_OFF || + ops->tz_flag == MSM_SCM_L2_GDHS) coresight_cti_ctx_restore(); switch (mode) { case MSM_SPM_MODE_POWER_COLLAPSE: - cpu_ops->tz_flag = MSM_SCM_L2_OFF; + ops->tz_flag = MSM_SCM_L2_OFF; coresight_cti_ctx_save(); break; case MSM_SPM_MODE_GDHS: - cpu_ops->tz_flag = MSM_SCM_L2_GDHS; + ops->tz_flag = MSM_SCM_L2_GDHS; coresight_cti_ctx_save(); break; case MSM_SPM_MODE_CLOCK_GATING: case MSM_SPM_MODE_RETENTION: case MSM_SPM_MODE_DISABLED: - cpu_ops->tz_flag = MSM_SCM_L2_ON; + ops->tz_flag = MSM_SCM_L2_ON; break; default: - cpu_ops->tz_flag = MSM_SCM_L2_ON; + ops->tz_flag = MSM_SCM_L2_ON; lpm = MSM_SPM_MODE_DISABLED; break; } - /* Do not program L2 SPM enable bit. This will be set by TZ */ - if (lpm_wa_get_skip_l2_spm()) - rc = msm_spm_config_low_power_mode_addr(ops->spm, lpm, - notify_rpm); - else - rc = msm_spm_config_low_power_mode(ops->spm, lpm, notify_rpm); + rc = msm_spm_config_low_power_mode(ops->spm, lpm, notify_rpm); + if (rc) pr_err("%s: Failed to set L2 low power mode %d, ERR %d", __func__, lpm, rc); @@ -249,23 +225,7 @@ int set_l2_mode(struct low_power_ops *ops, int mode, bool notify_rpm) return rc; } -int set_l3_mode(struct low_power_ops *ops, int mode, bool notify_rpm) -{ - struct low_power_ops *cpu_ops = per_cpu(cpu_cluster, - smp_processor_id())->lpm_dev; - - switch (mode) { - case MSM_SPM_MODE_POWER_COLLAPSE: - cpu_ops->tz_flag |= MSM_SCM_L3_PC_OFF; - break; - default: - break; - } - return msm_spm_config_low_power_mode(ops->spm, mode, notify_rpm); -} - - -int set_system_mode(struct low_power_ops *ops, int mode, bool notify_rpm) +int set_cci_mode(struct low_power_ops *ops, int mode, bool notify_rpm) { if (mode == MSM_SPM_MODE_CLOCK_GATING) mode = MSM_SPM_MODE_DISABLED; @@ -273,18 +233,21 @@ int set_system_mode(struct low_power_ops *ops, int mode, bool notify_rpm) } static int cpu_power_select(struct cpuidle_device *dev, - struct lpm_cpu *cpu) + struct lpm_cpu *cpu, int *index) { int best_level = -1; + uint32_t best_level_pwr = ~0U; uint32_t latency_us = pm_qos_request_for_cpu(PM_QOS_CPU_DMA_LATENCY, dev->cpu); uint32_t sleep_us = (uint32_t)(ktime_to_us(tick_nohz_get_sleep_length())); uint32_t modified_time_us = 0; uint32_t next_event_us = 0; + uint32_t pwr; int i; uint32_t lvl_latency_us = 0; - uint32_t *residency = get_per_cpu_max_residency(dev->cpu); + uint32_t lvl_overhead_us = 0; + uint32_t lvl_overhead_energy = 0; if (!cpu) return -EINVAL; @@ -308,11 +271,12 @@ static int cpu_power_select(struct cpuidle_device *dev, lvl_latency_us = pwr_params->latency_us; - if (i > 0 && suspend_in_progress) - continue; + lvl_overhead_us = pwr_params->time_overhead_us; + + lvl_overhead_energy = pwr_params->energy_overhead; if (latency_us < lvl_latency_us) - break; + continue; if (next_event_us) { if (next_event_us < lvl_latency_us) @@ -323,23 +287,38 @@ static int cpu_power_select(struct cpuidle_device *dev, next_wakeup_us = next_event_us - lvl_latency_us; } - if (next_wakeup_us <= residency[i]) { + if (next_wakeup_us <= pwr_params->time_overhead_us) + continue; + + /* + * If wakeup time greater than overhead by a factor of 1000 + * assume that core steady state power dominates the power + * equation + */ + if ((next_wakeup_us >> 10) > lvl_overhead_us) { + pwr = pwr_params->ss_power; + } else { + pwr = pwr_params->ss_power; + pwr -= (lvl_overhead_us * pwr_params->ss_power) / + next_wakeup_us; + pwr += pwr_params->energy_overhead / next_wakeup_us; + } + + if (best_level_pwr >= pwr) { best_level = i; + best_level_pwr = pwr; if (next_event_us && next_event_us < sleep_us && (mode != MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)) modified_time_us = next_event_us - lvl_latency_us; else modified_time_us = 0; - break; } } if (modified_time_us) msm_pm_set_timer(modified_time_us); - trace_cpu_power_select(best_level, sleep_us, latency_us, next_event_us); - return best_level; } @@ -353,19 +332,18 @@ static uint64_t get_cluster_sleep_time(struct lpm_cluster *cluster, struct cpumask online_cpus_in_cluster; next_event.tv64 = KTIME_MAX; - if (!suspend_wake_time) - suspend_wake_time = msm_pm_sleep_time_override; + if (!from_idle) { if (mask) cpumask_copy(mask, cpumask_of(raw_smp_processor_id())); - if (!suspend_wake_time) + if (!msm_pm_sleep_time_override) return ~0ULL; else - return USEC_PER_SEC * suspend_wake_time; + return USEC_PER_SEC * msm_pm_sleep_time_override; } - cpumask_and(&online_cpus_in_cluster, - &cluster->num_children_in_sync, cpu_online_mask); + BUG_ON(!cpumask_and(&online_cpus_in_cluster, + &cluster->num_childs_in_sync, cpu_online_mask)); for_each_cpu(cpu, &online_cpus_in_cluster) { td = &per_cpu(tick_cpu_device, cpu); @@ -389,6 +367,8 @@ static int cluster_select(struct lpm_cluster *cluster, bool from_idle) { int best_level = -1; int i; + uint32_t best_level_pwr = ~0U; + uint32_t pwr; struct cpumask mask; uint32_t latency_us = ~0U; uint32_t sleep_us; @@ -424,7 +404,7 @@ static int cluster_select(struct lpm_cluster *cluster, bool from_idle) cpumask_weight(cpu_online_mask) > 1) continue; - if (!cpumask_equal(&cluster->num_children_in_sync, + if (!cpumask_equal(&cluster->num_childs_in_sync, &level->num_cpu_votes)) continue; @@ -440,9 +420,18 @@ static int cluster_select(struct lpm_cluster *cluster, bool from_idle) if (level->notify_rpm && msm_rpm_waiting_for_ack()) continue; - if (sleep_us <= pwr_params->max_residency) { + if ((sleep_us >> 10) > pwr_params->time_overhead_us) { + pwr = pwr_params->ss_power; + } else { + pwr = pwr_params->ss_power; + pwr -= (pwr_params->time_overhead_us * + pwr_params->ss_power) / sleep_us; + pwr += pwr_params->energy_overhead / sleep_us; + } + + if (best_level_pwr >= pwr) { best_level = i; - break; + best_level_pwr = pwr; } } @@ -457,18 +446,18 @@ static int cluster_configure(struct lpm_cluster *cluster, int idx, spin_lock(&cluster->sync_lock); - if (!cpumask_equal(&cluster->num_children_in_sync, &cluster->child_cpus) - || is_IPI_pending(&cluster->num_children_in_sync)) { + if (!cpumask_equal(&cluster->num_childs_in_sync, &cluster->child_cpus) + || is_IPI_pending(&cluster->num_childs_in_sync)) { spin_unlock(&cluster->sync_lock); return -EPERM; } if (idx != cluster->default_level) { update_debug_pc_event(CLUSTER_ENTER, idx, - cluster->num_children_in_sync.bits[0], + cluster->num_childs_in_sync.bits[0], cluster->child_cpus.bits[0], from_idle); trace_cluster_enter(cluster->cluster_name, idx, - cluster->num_children_in_sync.bits[0], + cluster->num_childs_in_sync.bits[0], cluster->child_cpus.bits[0], from_idle); lpm_stats_cluster_enter(cluster->stats, idx); } @@ -488,20 +477,19 @@ static int cluster_configure(struct lpm_cluster *cluster, int idx, cpu_cluster_pm_enter(cluster->aff_level); } if (level->notify_rpm) { - struct cpumask nextcpu, *cpumask; - uint64_t us; + struct cpumask nextcpu; + uint32_t us; us = get_cluster_sleep_time(cluster, &nextcpu, from_idle); - cpumask = level->disable_dynamic_routing ? NULL : &nextcpu; - ret = msm_rpm_enter_sleep(0, cpumask); + ret = msm_rpm_enter_sleep(0, &nextcpu); if (ret) { pr_info("Failed msm_rpm_enter_sleep() rc = %d\n", ret); goto failed_set_mode; } do_div(us, USEC_PER_SEC/SCLK_HZ); - msm_mpm_enter_sleep(us, from_idle, cpumask); + msm_mpm_enter_sleep((uint32_t)us, from_idle, &nextcpu); } cluster->last_level = idx; spin_unlock(&cluster->sync_lock); @@ -532,8 +520,8 @@ static void cluster_prepare(struct lpm_cluster *cluster, return; spin_lock(&cluster->sync_lock); - cpumask_or(&cluster->num_children_in_sync, cpu, - &cluster->num_children_in_sync); + cpumask_or(&cluster->num_childs_in_sync, cpu, + &cluster->num_childs_in_sync); for (i = 0; i < cluster->nlevels; i++) { struct lpm_cluster_level *lvl = &cluster->levels[i]; @@ -550,7 +538,7 @@ static void cluster_prepare(struct lpm_cluster *cluster, * configuration process */ - if (!cpumask_equal(&cluster->num_children_in_sync, + if (!cpumask_equal(&cluster->num_childs_in_sync, &cluster->child_cpus)) { spin_unlock(&cluster->sync_lock); return; @@ -565,7 +553,7 @@ static void cluster_prepare(struct lpm_cluster *cluster, if (cluster_configure(cluster, i, from_idle)) return; - cluster_prepare(cluster->parent, &cluster->num_children_in_sync, i, + cluster_prepare(cluster->parent, &cluster->num_childs_in_sync, i, from_idle); } @@ -584,10 +572,10 @@ static void cluster_unprepare(struct lpm_cluster *cluster, spin_lock(&cluster->sync_lock); last_level = cluster->default_level; - first_cpu = cpumask_equal(&cluster->num_children_in_sync, + first_cpu = cpumask_equal(&cluster->num_childs_in_sync, &cluster->child_cpus); - cpumask_andnot(&cluster->num_children_in_sync, - &cluster->num_children_in_sync, cpu); + cpumask_andnot(&cluster->num_childs_in_sync, + &cluster->num_childs_in_sync, cpu); for (i = 0; i < cluster->nlevels; i++) { struct lpm_cluster_level *lvl = &cluster->levels[i]; @@ -605,14 +593,21 @@ static void cluster_unprepare(struct lpm_cluster *cluster, level = &cluster->levels[cluster->last_level]; if (level->notify_rpm) { msm_rpm_exit_sleep(); + + /* If RPM bumps up CX to turbo, unvote CX turbo vote + * during exit of rpm assisted power collapse to + * reduce the power impact + */ + + lpm_wa_cx_unvote_send(); msm_mpm_exit_sleep(from_idle); } update_debug_pc_event(CLUSTER_EXIT, cluster->last_level, - cluster->num_children_in_sync.bits[0], + cluster->num_childs_in_sync.bits[0], cluster->child_cpus.bits[0], from_idle); trace_cluster_exit(cluster->cluster_name, cluster->last_level, - cluster->num_children_in_sync.bits[0], + cluster->num_childs_in_sync.bits[0], cluster->child_cpus.bits[0], from_idle); last_level = cluster->last_level; @@ -678,32 +673,28 @@ static inline void cpu_unprepare(struct lpm_cluster *cluster, int cpu_index, cpu_pm_exit(); } -static int lpm_cpuidle_select(struct cpuidle_driver *drv, - struct cpuidle_device *dev) -{ - struct lpm_cluster *cluster = per_cpu(cpu_cluster, dev->cpu); - int idx; - - if (!cluster) - return 0; - - idx = cpu_power_select(dev, cluster->cpu); - - if (idx < 0) - return -EPERM; - - return idx; -} - static int lpm_cpuidle_enter(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int idx) + struct cpuidle_driver *drv, int index) { struct lpm_cluster *cluster = per_cpu(cpu_cluster, dev->cpu); + int64_t time = ktime_to_ns(ktime_get()); bool success = true; + int idx = cpu_power_select(dev, cluster->cpu, &index); const struct cpumask *cpumask = get_cpu_mask(dev->cpu); - int64_t start_time = ktime_to_ns(ktime_get()), end_time; struct power_params *pwr_params; + if (idx < 0) { + local_irq_enable(); + return -EPERM; + } + + trace_cpu_idle_rcuidle(idx, dev->cpu); + + if (need_resched()) { + dev->last_residency = 0; + goto exit; + } + pwr_params = &cluster->cpu->levels[idx].pwr; sched_set_cpu_cstate(smp_processor_id(), idx + 1, &drv->states[idx+1], pwr_params->energy_overhead, pwr_params->latency_us); @@ -711,37 +702,28 @@ static int lpm_cpuidle_enter(struct cpuidle_device *dev, cpu_prepare(cluster, idx, true); cluster_prepare(cluster, cpumask, idx, true); - trace_cpu_idle_enter(idx); lpm_stats_cpu_enter(idx); - - if (need_resched() || (idx < 0)) - goto exit; - - if (idx > 0) - update_debug_pc_event(CPU_ENTER, idx, 0xdeaffeed, 0xdeaffeed, - true); - success = msm_cpu_pm_enter_sleep(cluster->cpu->levels[idx].mode, - true); - if (idx > 0) - update_debug_pc_event(CPU_EXIT, idx, success, 0xdeaffeed, - true); - -exit: + success = msm_cpu_pm_enter_sleep(cluster->cpu->levels[idx].mode, true); lpm_stats_cpu_exit(idx, success); + trace_cpu_idle_exit(idx, success); cluster_unprepare(cluster, cpumask, idx, true); cpu_unprepare(cluster, idx, true); sched_set_cpu_cstate(smp_processor_id(), 0, &drv->states[0], 0, 0); - trace_cpu_idle_exit(idx, success); - end_time = ktime_to_ns(ktime_get()) - start_time; - dev->last_residency = do_div(end_time, 1000); - local_irq_enable(); + time = ktime_to_ns(ktime_get()) - time; + do_div(time, 1000); + dev->last_residency = (int)time; + +exit: + local_irq_enable(); + trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, dev->cpu); return idx; } #ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS +static DEFINE_PER_CPU(struct cpuidle_device, cpuidle_dev); static int cpuidle_register_cpu(struct cpuidle_driver *drv, struct cpumask *mask) { @@ -752,14 +734,12 @@ static int cpuidle_register_cpu(struct cpuidle_driver *drv, if (!mask || !drv) return -EINVAL; - drv->cpumask = mask; - ret = cpuidle_register_driver(drv); - if (ret) { - pr_err("Failed to register cpuidle driver %d\n", ret); - goto failed_driver_register; - } - for_each_cpu(cpu, mask) { + ret = cpuidle_register_cpu_driver(drv, cpu); + if (ret) { + pr_err("Failed to register cpuidle driver %d\n", ret); + goto failed_driver_register; + } device = &per_cpu(cpuidle_dev, cpu); device->cpu = cpu; @@ -773,7 +753,7 @@ static int cpuidle_register_cpu(struct cpuidle_driver *drv, return ret; failed_driver_register: for_each_cpu(cpu, mask) - cpuidle_unregister_driver(drv); + cpuidle_unregister_cpu_driver(drv, cpu); return ret; } #else @@ -784,13 +764,6 @@ static int cpuidle_register_cpu(struct cpuidle_driver *drv, } #endif -static struct cpuidle_governor lpm_governor = { - .name = "qcom", - .rating = 30, - .select = lpm_cpuidle_select, - .owner = THIS_MODULE, -}; - static int cluster_cpuidle_register(struct lpm_cluster *cl) { int i = 0, ret = 0; @@ -838,10 +811,10 @@ static int cluster_cpuidle_register(struct lpm_cluster *cl) while (p) { int j; spin_lock(&p->sync_lock); - cpumask_set_cpu(cpu, &p->num_children_in_sync); + cpumask_set_cpu(cpu, &p->num_childs_in_sync); for (j = 0; j < p->nlevels; j++) cpumask_copy(&p->levels[j].num_cpu_votes, - &p->num_children_in_sync); + &p->num_childs_in_sync); spin_unlock(&p->sync_lock); p = p->parent; } @@ -852,19 +825,10 @@ static int cluster_cpuidle_register(struct lpm_cluster *cl) kfree(cl->drv); return -ENOMEM; } - return 0; -} -/** - * init_lpm - initializes the governor - */ -static int __init init_lpm(void) -{ - return cpuidle_register_governor(&lpm_governor); + return 0; } -postcore_initcall(init_lpm); - static void register_cpu_lpm_stats(struct lpm_cpu *cpu, struct lpm_cluster *parent) { @@ -926,7 +890,7 @@ static int lpm_suspend_prepare(void) return 0; } -static void lpm_suspend_end(void) +static void lpm_suspend_wake(void) { suspend_in_progress = false; msm_mpm_suspend_wake(); @@ -953,13 +917,7 @@ static int lpm_suspend_enter(suspend_state_t state) } cpu_prepare(cluster, idx, false); cluster_prepare(cluster, cpumask, idx, false); - if (idx > 0) - update_debug_pc_event(CPU_ENTER, idx, 0xdeaffeed, - 0xdeaffeed, false); msm_cpu_pm_enter_sleep(cluster->cpu->levels[idx].mode, false); - if (idx > 0) - update_debug_pc_event(CPU_EXIT, idx, true, 0xdeaffeed, - false); cluster_unprepare(cluster, cpumask, idx, false); cpu_unprepare(cluster, idx, false); return 0; @@ -969,8 +927,7 @@ static const struct platform_suspend_ops lpm_suspend_ops = { .enter = lpm_suspend_enter, .valid = suspend_valid_only_mem, .prepare_late = lpm_suspend_prepare, - .end = lpm_suspend_end, - .suspend_again = quickwakeup_suspend_again, + .wake = lpm_suspend_wake, }; static int lpm_probe(struct platform_device *pdev) @@ -979,12 +936,10 @@ static int lpm_probe(struct platform_device *pdev) int size; struct kobject *module_kobj = NULL; - get_online_cpus(); lpm_root_node = lpm_of_parse_cluster(pdev); if (IS_ERR_OR_NULL(lpm_root_node)) { pr_err("%s(): Failed to probe low power modes\n", __func__); - put_online_cpus(); return PTR_ERR(lpm_root_node); } @@ -997,6 +952,7 @@ static int lpm_probe(struct platform_device *pdev) * core. BUG in existing code but no known issues possibly because of * how late lpm_levels gets initialized. */ + register_hotcpu_notifier(&lpm_cpu_nblk); get_cpu(); on_each_cpu(setup_broadcast_timer, (void *)true, 1); put_cpu(); @@ -1007,7 +963,6 @@ static int lpm_probe(struct platform_device *pdev) if (ret) { pr_err("%s: Failed initializing scm_handoff_lock (%d)\n", __func__, ret); - put_online_cpus(); return ret; } @@ -1017,13 +972,12 @@ static int lpm_probe(struct platform_device *pdev) register_cluster_lpm_stats(lpm_root_node, NULL); ret = cluster_cpuidle_register(lpm_root_node); - put_online_cpus(); if (ret) { pr_err("%s()Failed to register with cpuidle framework\n", __func__); goto failed; } - register_hotcpu_notifier(&lpm_cpu_nblk); + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); if (!module_kobj) { pr_err("%s: cannot find kobject for module %s\n", @@ -1101,8 +1055,7 @@ enum msm_pm_l2_scm_flag lpm_cpu_pre_pc_cb(unsigned int cpu) goto unlock_and_return; } - if (!cpumask_equal(&cluster->num_children_in_sync, - &cluster->child_cpus)) + if (!cpumask_equal(&cluster->num_childs_in_sync, &cluster->child_cpus)) goto unlock_and_return; if (cluster->lpm_dev) @@ -1178,4 +1131,3 @@ void lpm_cpu_hotplug_enter(unsigned int cpu) msm_cpu_pm_enter_sleep(mode, false); } - diff --git a/drivers/cpuidle/lpm-levels.h b/drivers/cpuidle/lpm-levels.h index 5a48fb2c94564..04e46c7cebab8 100644 --- a/drivers/cpuidle/lpm-levels.h +++ b/drivers/cpuidle/lpm-levels.h @@ -25,8 +25,6 @@ struct power_params { uint32_t ss_power; /* Steady state power */ uint32_t energy_overhead; /* Enter + exit over head */ uint32_t time_overhead_us; /* Enter + exit overhead */ - uint32_t residencies[NR_LPM_LEVELS]; - uint32_t max_residency; }; struct lpm_cpu_level { @@ -48,9 +46,6 @@ struct lpm_level_avail { struct kobject *kobj; struct kobj_attribute idle_enabled_attr; struct kobj_attribute suspend_enabled_attr; - void *data; - int idx; - bool cpu_node; }; struct lpm_cluster_level { @@ -60,7 +55,6 @@ struct lpm_cluster_level { struct cpumask num_cpu_votes; struct power_params pwr; bool notify_rpm; - bool disable_dynamic_routing; bool sync_level; bool last_core_only; struct lpm_level_avail available; @@ -90,16 +84,14 @@ struct lpm_cluster { struct cpuidle_driver *drv; spinlock_t sync_lock; struct cpumask child_cpus; - struct cpumask num_children_in_sync; + struct cpumask num_childs_in_sync; struct lpm_cluster *parent; struct lpm_stats *stats; bool no_saw_devices; }; int set_l2_mode(struct low_power_ops *ops, int mode, bool notify_rpm); -void lpm_suspend_wake_time(uint64_t wakeup_time); -int set_system_mode(struct low_power_ops *ops, int mode, bool notify_rpm); -int set_l3_mode(struct low_power_ops *ops, int mode, bool notify_rpm); +int set_cci_mode(struct low_power_ops *ops, int mode, bool notify_rpm); struct lpm_cluster *lpm_of_parse_cluster(struct platform_device *pdev); void free_cluster_node(struct lpm_cluster *cluster); @@ -110,7 +102,7 @@ bool lpm_cpu_mode_allow(unsigned int cpu, unsigned int mode, bool from_idle); bool lpm_cluster_mode_allow(struct lpm_cluster *cluster, unsigned int mode, bool from_idle); -uint32_t *get_per_cpu_max_residency(int cpu); + extern struct lpm_cluster *lpm_root_node; #ifdef CONFIG_SMP diff --git a/drivers/cpuidle/lpm-workarounds.c b/drivers/cpuidle/lpm-workarounds.c index 3311ff2ad5307..104def7e71e48 100644 --- a/drivers/cpuidle/lpm-workarounds.c +++ b/drivers/cpuidle/lpm-workarounds.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -13,284 +13,96 @@ #include #include -#include #include #include -#include #include #include #include #include -#include -#include -#include -#include -#define L2_HS_STS_SET 0x200 - -static struct work_struct lpm_wa_work; +static struct regulator *lpm_cx_reg; +static struct work_struct dummy_vote_work; static struct workqueue_struct *lpm_wa_wq; -static bool skip_l2_spm; -static bool enable_dynamic_clock_gating; -static bool is_l1_l2_gcc_secure; -static bool store_clock_gating; -int non_boot_cpu_index; -void __iomem *l2_pwr_sts; -void __iomem *l1_l2_gcc; -struct device_clnt_data *hotplug_handle; -union device_request curr_req; -cpumask_t l1_l2_offline_mask; -cpumask_t offline_mask; -struct resource *l1_l2_gcc_res; -uint32_t l2_status = -1; +static bool lpm_wa_cx_turbo_unvote; -static int lpm_wa_callback(struct notifier_block *cpu_nb, - unsigned long action, void *hcpu) +/* While exiting from RPM assisted power collapse on some targets like MSM8939 + * the CX is bumped to turbo mode by RPM. To reduce the power impact, APSS + * low power driver need to remove the CX turbo vote. + */ +static void send_dummy_cx_vote(struct work_struct *w) { - unsigned long cpu = (unsigned long) hcpu; + if (lpm_cx_reg) { + regulator_set_voltage(lpm_cx_reg, + RPM_REGULATOR_CORNER_SUPER_TURBO, + RPM_REGULATOR_CORNER_SUPER_TURBO); - if ((action != CPU_POST_DEAD) && (action != CPU_ONLINE)) - return NOTIFY_OK; - if (cpu >= non_boot_cpu_index) - return NOTIFY_OK; - - switch (action & ~CPU_TASKS_FROZEN) { - case CPU_POST_DEAD: - cpumask_set_cpu(cpu, &offline_mask); - break; - case CPU_ONLINE: - cpumask_clear_cpu(cpu, &offline_mask); - break; - default: - break; + regulator_set_voltage(lpm_cx_reg, + RPM_REGULATOR_CORNER_NONE, + RPM_REGULATOR_CORNER_SUPER_TURBO); } - - if (cpumask_equal(&offline_mask, &l1_l2_offline_mask) - && store_clock_gating) - queue_work(lpm_wa_wq, &lpm_wa_work); - - return NOTIFY_OK; } -static struct notifier_block __refdata lpm_wa_nblk = { - .notifier_call = lpm_wa_callback, -}; - -static void process_lpm_workarounds(struct work_struct *w) +/* + * lpm_wa_cx_unvote_send(): Unvote for CX turbo mode + */ +void lpm_wa_cx_unvote_send(void) { - int ret = 0, status = 0; - - /* MSM8952 have L1/L2 dynamic clock gating disabled in HW for - * performance cluster cores. Enable it via SW to reduce power - * impact. - */ - - if (enable_dynamic_clock_gating) { - - /* Skip enabling L1/L2 clock gating if perf l2 is not in low - * power mode. - */ - - status = (__raw_readl(l2_pwr_sts) & L2_HS_STS_SET) - == L2_HS_STS_SET; - if (status) { - pr_err("%s: perf L2 is not in low power mode\n", - __func__); - queue_work(lpm_wa_wq, &lpm_wa_work); - return; - } - - l2_status = __raw_readl(l2_pwr_sts); - pr_err("Set L1_L2_GCC from cpu%d when perf L2 status=0x%x\n", - smp_processor_id(), l2_status); - - if (is_l1_l2_gcc_secure) { - scm_io_write((u32)(l1_l2_gcc_res->start), 0x0); - if (scm_io_read((u32)(l1_l2_gcc_res->start)) != 0) - pr_err("Failed to set L1_L2_GCC\n"); - } else { - __raw_writel(0x0, l1_l2_gcc); - if (__raw_readl(l1_l2_gcc) != 0x0) - pr_err("Failed to set L1_L2_GCC\n"); - } - HOTPLUG_NO_MITIGATION(&curr_req.offline_mask); - ret = devmgr_client_request_mitigation( - hotplug_handle, - HOTPLUG_MITIGATION_REQ, - &curr_req); - if (ret) { - pr_err("hotplug request failed. err:%d\n", ret); - return; - } - enable_dynamic_clock_gating = false; - unregister_hotcpu_notifier(&lpm_wa_nblk); - } + if (lpm_wa_cx_turbo_unvote) + queue_work(lpm_wa_wq, &dummy_vote_work); } +EXPORT_SYMBOL(lpm_wa_cx_unvote_send); -bool lpm_wa_get_skip_l2_spm(void) +static int lpm_wa_cx_unvote_init(struct platform_device *pdev) { - return skip_l2_spm; -} -EXPORT_SYMBOL(lpm_wa_get_skip_l2_spm); + int ret = 0; + lpm_cx_reg = devm_regulator_get(&pdev->dev, "lpm-cx"); + if (IS_ERR(lpm_cx_reg)) { + ret = PTR_ERR(lpm_cx_reg); + if (ret != -EPROBE_DEFER) + pr_err("Unable to get the CX regulator\n"); + return ret; + } + INIT_WORK(&dummy_vote_work, send_dummy_cx_vote); -static ssize_t store_clock_gating_enabled(struct kobject *kobj, - struct kobj_attribute *attr, const char *buf, size_t count) -{ - int ret = 0, val = 0; + lpm_wa_wq = alloc_workqueue("lpm-wa", + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI, 1); - ret = kstrtoint(buf, 10, &val); - if (ret) { - pr_err("Invalid input%s %s. err:%d\n", __func__, buf, ret); - return count; - } + return ret; +} - cpumask_copy(&curr_req.offline_mask, &l1_l2_offline_mask); - ret = devmgr_client_request_mitigation( - hotplug_handle, - HOTPLUG_MITIGATION_REQ, - &curr_req); - if (ret) { - pr_err("hotplug request failed. err:%d\n", ret); - return count; - } +static int lpm_wa_cx_unvote_exit(void) +{ + if (lpm_wa_wq) + destroy_workqueue(lpm_wa_wq); - store_clock_gating = true; - return count; + return 0; } -static struct kobj_attribute clock_gating_enabled_attr = -__ATTR(dynamic_clock_gating, 0644, NULL, store_clock_gating_enabled); - static int lpm_wa_probe(struct platform_device *pdev) { - int ret = 0, c = 0, cpu = 0; - struct kobject *module_kobj = NULL; - struct kobject *module_lpm_wa = NULL; - struct resource *res = NULL; - unsigned long cpu_mask = 0; - char *key; - - - skip_l2_spm = of_property_read_bool(pdev->dev.of_node, - "qcom,lpm-wa-skip-l2-spm"); - - /* - * Enabling L1/L2 tag ram clock gating requires core and L2 to be - * in quiescent state. lpm-wa-dynamic-clock-gating flag specifies - * WA implementation in SW for perf core0 and L2. - */ - enable_dynamic_clock_gating = of_property_read_bool(pdev->dev.of_node, - "qcom,lpm-wa-dynamic-clock-gating"); - if (!enable_dynamic_clock_gating) - return ret; - is_l1_l2_gcc_secure = of_property_read_bool(pdev->dev.of_node, - "qcom,l1_l2_gcc_secure"); - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "l2-pwr-sts"); - if (!res) { - pr_err("%s(): Missing perf l2-pwr-sts resource\n", - __func__); - return -EINVAL; - } - - l2_pwr_sts = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (!l2_pwr_sts) { - pr_err("%s: cannot ioremap l2_pwr_status %s\n", __func__, - KBUILD_MODNAME); - return -ENOMEM; - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "l1-l2-gcc"); - if (!res) { - pr_err("%s(): Missing L1/L2 GCC override resource\n", - __func__); - return -EINVAL; - } - l1_l2_gcc_res = res; - - l1_l2_gcc = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (!l1_l2_gcc) { - pr_err("%s: cannot ioremap L1/L2 GCC override %s\n", - __func__, KBUILD_MODNAME); - return -ENOMEM; - } - - key = "qcom,non-boot-cpu-index"; - ret = of_property_read_u32(pdev->dev.of_node, key, - &non_boot_cpu_index); - if (!ret) { - pr_err("%s: Missing qcom,non_boot_cpu_index property\n" - , __func__); - } + int ret = 0; - key = "qcom,cpu-offline-mask"; - ret = of_property_read_u32(pdev->dev.of_node, key, - (u32 *) &cpu_mask); - if (!ret) { - for_each_set_bit(c, &cpu_mask, num_possible_cpus()) { - cpumask_set_cpu(c, &l1_l2_offline_mask); + lpm_wa_cx_turbo_unvote = of_property_read_bool(pdev->dev.of_node, + "qcom,lpm-wa-cx-turbo-unvote"); + if (lpm_wa_cx_turbo_unvote) { + ret = lpm_wa_cx_unvote_init(pdev); + if (ret) { + pr_err("%s: Failed to initialize lpm_wa_cx_unvote (%d)\n", + __func__, ret); + return ret; } } - module_kobj = kset_find_obj(module_kset, "lpm_levels"); - if (!module_kobj) { - pr_err("%s: cannot find kobject for module lpm_levels\n", - __func__); - ret = -ENOENT; - } - - module_lpm_wa = kobject_create_and_add("lpm_workarounds", - module_kobj); - if (!module_lpm_wa) { - pr_err("%s: cannot create kobject for module %s\n", - __func__, KBUILD_MODNAME); - ret = -ENOENT; - } - - ret = sysfs_create_file(module_lpm_wa, - &clock_gating_enabled_attr.attr); - if (ret) { - pr_err("cannot create attr group. err:%d\n", ret); - return ret; - } - - hotplug_handle = devmgr_register_mitigation_client(&pdev->dev, - HOTPLUG_DEVICE, NULL); - if (IS_ERR_OR_NULL(hotplug_handle)) { - ret = PTR_ERR(hotplug_handle); - pr_err("Error registering for hotplug. ret:%d\n", ret); - return ret; - } - - INIT_WORK(&lpm_wa_work, process_lpm_workarounds); - - lpm_wa_wq = alloc_workqueue("lpm-wa", - WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI, 1); - if (IS_ERR_OR_NULL(lpm_wa_wq)) { - ret = PTR_ERR(lpm_wa_wq); - pr_err("%s: Failed to allocate workqueue\n", __func__); - return ret; - } - - for_each_present_cpu(cpu) { - if (cpu < non_boot_cpu_index) { - if (!cpumask_test_cpu(cpu, cpu_online_mask)) - cpumask_set_cpu(cpu, &offline_mask); - } - } - register_hotcpu_notifier(&lpm_wa_nblk); return ret; } static int lpm_wa_remove(struct platform_device *pdev) { int ret = 0; + if (lpm_wa_cx_turbo_unvote) + ret = lpm_wa_cx_unvote_exit(); return ret; } diff --git a/drivers/cpuidle/lpm-workarounds.h b/drivers/cpuidle/lpm-workarounds.h index a296112f96168..c743691e1f585 100644 --- a/drivers/cpuidle/lpm-workarounds.h +++ b/drivers/cpuidle/lpm-workarounds.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -15,5 +15,5 @@ #define __LPM_WA_H void lpm_wa_cx_unvote_send(void); -bool lpm_wa_get_skip_l2_spm(void); + #endif /* __LPM_WA_H */ diff --git a/drivers/devfreq/devfreq_spdm_debugfs.c b/drivers/devfreq/devfreq_spdm_debugfs.c index 94e94f3bbc1c7..1d6a581341a5f 100644 --- a/drivers/devfreq/devfreq_spdm_debugfs.c +++ b/drivers/devfreq/devfreq_spdm_debugfs.c @@ -34,7 +34,7 @@ static ssize_t enable_write(struct file *file, const char __user *data, int i; int next_idx; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { @@ -42,6 +42,8 @@ static ssize_t enable_write(struct file *file, const char __user *data, size = -EINVAL; } + buf[size] = '\0'; + if (sscanf(buf, "%u\n", &i) != 1) { size = -EINVAL; goto err; @@ -105,7 +107,7 @@ static ssize_t pl_write(struct file *file, const char __user *data, int ext_status = 0; int i; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { @@ -113,6 +115,8 @@ static ssize_t pl_write(struct file *file, const char __user *data, goto out; } + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.pl_freqs[0], &spdm_data->config_data.pl_freqs[1]) != 2) { size = -EINVAL; @@ -164,7 +168,7 @@ static ssize_t rejrate_low_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { @@ -172,6 +176,8 @@ static ssize_t rejrate_low_write(struct file *file, const char __user *data, goto out; } + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.reject_rate[0], &spdm_data->config_data.reject_rate[1]) != 2) { size = -EINVAL; @@ -224,13 +230,16 @@ static ssize_t rejrate_med_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.reject_rate[2], &spdm_data->config_data.reject_rate[3]) != 2) { size = -EINVAL; @@ -282,13 +291,16 @@ static ssize_t rejrate_high_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.reject_rate[4], &spdm_data->config_data.reject_rate[5]) != 2) { size = -EINVAL; @@ -340,13 +352,16 @@ static ssize_t resptime_low_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.response_time_us[0], &spdm_data->config_data.response_time_us[1]) != 2) { size = -EINVAL; @@ -398,13 +413,16 @@ static ssize_t resptime_med_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.response_time_us[2], &spdm_data->config_data.response_time_us[3]) != 2) { size = -EINVAL; @@ -456,13 +474,16 @@ static ssize_t resptime_high_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.response_time_us[4], &spdm_data->config_data.response_time_us[5]) != 2) { size = -EINVAL; @@ -515,13 +536,16 @@ static ssize_t cciresptime_low_write(struct file *file, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.cci_response_time_us[0], &spdm_data->config_data.cci_response_time_us[1]) != 2) { @@ -575,13 +599,16 @@ static ssize_t cciresptime_med_write(struct file *file, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.cci_response_time_us[2], &spdm_data->config_data.cci_response_time_us[3]) != 2) { @@ -635,13 +662,16 @@ static ssize_t cciresptime_high_write(struct file *file, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u\n", &spdm_data->config_data.cci_response_time_us[4], &spdm_data->config_data.cci_response_time_us[5]) != 2){ @@ -694,13 +724,16 @@ static ssize_t cci_max_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u\n", &spdm_data->config_data.max_cci_freq) != 1) { size = -EINVAL; goto out; @@ -748,13 +781,16 @@ static ssize_t vote_cfg_write(struct file *file, const char __user *data, struct spdm_args desc = { { 0 } }; int ext_status = 0; - if (size > sizeof(buf)) + if (size > sizeof(buf) - 1) return -EINVAL; if (copy_from_user(buf, data, size)) { size = -EINVAL; goto out; } + + buf[size] = '\0'; + if (sscanf(buf, "%u %u %u %u\n", &spdm_data->config_data.upstep, &spdm_data->config_data.downstep, &spdm_data->config_data.max_vote, diff --git a/drivers/gpu/msm/kgsl.h b/drivers/gpu/msm/kgsl.h index 0711c43c31783..20de789fbd05e 100644 --- a/drivers/gpu/msm/kgsl.h +++ b/drivers/gpu/msm/kgsl.h @@ -30,7 +30,7 @@ /* The number of memstore arrays limits the number of contexts allowed. * If more contexts are needed, update multiple for MEMSTORE_SIZE */ -#define KGSL_MEMSTORE_SIZE ((int)(PAGE_SIZE * 2)) +#define KGSL_MEMSTORE_SIZE ((int)(PAGE_SIZE * 8)) #define KGSL_MEMSTORE_GLOBAL (0) #define KGSL_PRIORITY_MAX_RB_LEVELS 4 #define KGSL_MEMSTORE_MAX (KGSL_MEMSTORE_SIZE / \ diff --git a/drivers/i2c/busses/i2c-msm-v2.c b/drivers/i2c/busses/i2c-msm-v2.c index a5dd336bb2b5e..90c078a471262 100644 --- a/drivers/i2c/busses/i2c-msm-v2.c +++ b/drivers/i2c/busses/i2c-msm-v2.c @@ -2973,7 +2973,7 @@ static int i2c_msm_init(void) { return platform_driver_register(&i2c_msm_driver); } -arch_initcall(i2c_msm_init); +subsys_initcall(i2c_msm_init); static void i2c_msm_exit(void) { diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index bb469e32c6082..e16f43657689b 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -175,15 +175,12 @@ config INPUT_APMPOWER module will be called apm-power. config INPUT_KEYRESET - tristate "Reset key" + bool "Reset key" depends on INPUT select INPUT_KEYCOMBO ---help--- Say Y here if you want to reboot when some keys are pressed; - To compile this driver as a module, choose M here: the - module will be called keyreset. - config INPUT_KEYCOMBO tristate "Key combo" depends on INPUT diff --git a/drivers/input/misc/tri_state_key.c b/drivers/input/misc/tri_state_key.c index ebc4655b4f9e0..5a104f29029fd 100755 --- a/drivers/input/misc/tri_state_key.c +++ b/drivers/input/misc/tri_state_key.c @@ -36,23 +36,14 @@ 4�ź�3������ 1 0 | Normal */ -typedef enum { - MODE_UNKNOWN, - MODE_MUTE, - MODE_DO_NOT_DISTURB, - MODE_NORMAL, - MODE_MAX_NUM -} tri_mode_t; - -#define MODE_TOTAL_SILENCE 600 -#define MODE_ALARMS_ONLY 601 -#define MODE_PRIORITY_ONLY 602 -#define MODE_NONE 603 + +#define KEYCODE_BASE 600 +#define TOTAL_KEYCODES 6 static int current_mode = 0; -static int keyCode_slider_top = MODE_ALARMS_ONLY; -static int keyCode_slider_middle = MODE_PRIORITY_ONLY; -static int keyCode_slider_bottom = MODE_NONE; +static int keyCode_slider_top = KEYCODE_BASE + 1; +static int keyCode_slider_middle = KEYCODE_BASE + 2; +static int keyCode_slider_bottom = KEYCODE_BASE + 3; struct switch_dev_data { //tri_mode_t last_type; @@ -103,17 +94,17 @@ static void switch_dev_work(struct work_struct *work) mutex_lock(&sem); if(!gpio_get_value(switch_data->key2_gpio)) { - mode = MODE_NORMAL; + mode = 3; keyCode = keyCode_slider_bottom; } else if(gpio_get_value(switch_data->key1_gpio)) { - mode = MODE_DO_NOT_DISTURB; + mode = 2; keyCode = keyCode_slider_middle; } else { - mode = MODE_MUTE; + mode = 1; keyCode = keyCode_slider_top; } if (current_mode != mode) { @@ -319,7 +310,7 @@ static ssize_t keyCode_top_write(struct file *file, const char __user *page, siz if (sscanf(buf, "%d", &data) != 1) return t; - if (data < 600 || data > 603) + if (data < KEYCODE_BASE || data >= (KEYCODE_BASE + TOTAL_KEYCODES)) return t; keyCode_slider_top = data; @@ -363,7 +354,7 @@ static ssize_t keyCode_middle_write(struct file *file, const char __user *page, if (sscanf(buf, "%d", &data) != 1) return t; - if (data < 600 || data > 603) + if (data < KEYCODE_BASE || data >= (KEYCODE_BASE + TOTAL_KEYCODES)) return t; keyCode_slider_middle = data; @@ -407,7 +398,7 @@ static ssize_t keyCode_bottom_write(struct file *file, const char __user *page, if (sscanf(buf, "%d", &data) != 1) return t; - if (data < 600 || data > 603) + if (data < KEYCODE_BASE || data >= (KEYCODE_BASE + TOTAL_KEYCODES)) return t; keyCode_slider_bottom = data; @@ -437,6 +428,7 @@ static int tristate_dev_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct proc_dir_entry *procdir; int error=0; + int i; //void __iomem *cfg_reg; @@ -464,10 +456,8 @@ static int tristate_dev_probe(struct platform_device *pdev) switch_data->input->name = DRV_NAME; switch_data->input->dev.parent = &pdev->dev; set_bit(EV_KEY, switch_data->input->evbit); - set_bit(MODE_TOTAL_SILENCE, switch_data->input->keybit); - set_bit(MODE_ALARMS_ONLY, switch_data->input->keybit); - set_bit(MODE_PRIORITY_ONLY, switch_data->input->keybit); - set_bit(MODE_NONE, switch_data->input->keybit); + for (i = KEYCODE_BASE; i < KEYCODE_BASE + TOTAL_KEYCODES; i++) + set_bit(i, switch_data->input->keybit); input_set_drvdata(switch_data->input, switch_data); error = input_register_device(switch_data->input); if (error) { diff --git a/drivers/input/tablet/gtco.c b/drivers/input/tablet/gtco.c index a9f8f925ba2b2..5a7be4c559f3b 100644 --- a/drivers/input/tablet/gtco.c +++ b/drivers/input/tablet/gtco.c @@ -232,13 +232,17 @@ static void parse_hid_report_descriptor(struct gtco *device, char * report, /* Walk this report and pull out the info we need */ while (i < length) { - prefix = report[i]; - - /* Skip over prefix */ - i++; + prefix = report[i++]; /* Determine data size and save the data in the proper variable */ - size = PREF_SIZE(prefix); + size = (1U << PREF_SIZE(prefix)) >> 1; + if (i + size > length) { + dev_err(ddev, + "Not enough data (need %d, have %d)\n", + i + size, length); + break; + } + switch (size) { case 1: data = report[i]; @@ -246,8 +250,7 @@ static void parse_hid_report_descriptor(struct gtco *device, char * report, case 2: data16 = get_unaligned_le16(&report[i]); break; - case 3: - size = 4; + case 4: data32 = get_unaligned_le32(&report[i]); break; } diff --git a/drivers/input/touchscreen/synaptics_rmi_dev.c b/drivers/input/touchscreen/synaptics_rmi_dev.c index 64f051e70e6af..acf201e3c142a 100644 --- a/drivers/input/touchscreen/synaptics_rmi_dev.c +++ b/drivers/input/touchscreen/synaptics_rmi_dev.c @@ -73,19 +73,19 @@ struct rmidev_data { }; static struct device_attribute attrs[] = { - __ATTR(open, S_IWUSR | S_IWGRP, + __ATTR(open, S_IWUSR, NULL, rmidev_sysfs_open_store), - __ATTR(release, S_IWUSR | S_IWGRP, + __ATTR(release, S_IWUSR, NULL, rmidev_sysfs_release_store), - __ATTR(address, S_IWUSR | S_IWGRP, + __ATTR(address, S_IWUSR, NULL, rmidev_sysfs_address_store), - __ATTR(length, S_IWUSR | S_IWGRP, + __ATTR(length, S_IWUSR, NULL, rmidev_sysfs_length_store), - __ATTR(data, (S_IWUSR | S_IWGRP), + __ATTR(data, S_IWUSR, rmidev_sysfs_data_show, rmidev_sysfs_data_store), }; diff --git a/drivers/leds/leds-qpnp.c b/drivers/leds/leds-qpnp.c index b0dcfe7845a46..71949d5c82993 100644 --- a/drivers/leds/leds-qpnp.c +++ b/drivers/leds/leds-qpnp.c @@ -267,6 +267,8 @@ enum qpnp_leds { QPNP_ID_MAX, }; +#define QPNP_ID_TO_RGB_IDX(id) (id - QPNP_ID_RGB_RED) + /* current boost limit */ enum wled_current_boost_limit { WLED_CURR_LIMIT_105mA, @@ -556,6 +558,15 @@ struct qpnp_led_data { int turn_off_delay_ms; }; +/** + * struct rgb_sync - rgb led synchrnize structure + */ +struct rgb_sync { + struct led_classdev cdev; + struct spmi_device *spmi_dev; + struct qpnp_led_data *led_data[3]; +}; + static DEFINE_MUTEX(flash_lock); static struct pwm_device *kpdbl_master; static u32 kpdbl_master_period_us; @@ -2690,6 +2701,130 @@ static ssize_t blink_store(struct device *dev, return count; } +static inline void rgb_lock_leds(struct rgb_sync *rgb) +{ + int i; + + for (i = 0; i < 3; i++) { + if (rgb->led_data[i]) { + flush_work(&rgb->led_data[i]->work); + mutex_lock(&rgb->led_data[i]->lock); + } + } +} + +static inline void rgb_unlock_leds(struct rgb_sync *rgb) +{ + int i; + + for (i = 0; i < 3; i++) { + if (rgb->led_data[i]) { + mutex_unlock(&rgb->led_data[i]->lock); + } + } +} + +static void rgb_disable_leds(struct rgb_sync *rgb) +{ + int i; + struct qpnp_led_data *led; + + //TODO Implement synchronized off + for (i = 0; i < 3; i++) { + led = rgb->led_data[i]; + if (led && led->rgb_cfg->pwm_cfg->pwm_enabled) { + led->rgb_cfg->pwm_cfg->mode = + led->rgb_cfg->pwm_cfg->default_mode; + led->rgb_cfg->pwm_cfg->blinking = false; + pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev); + led->rgb_cfg->pwm_cfg->pwm_enabled = 0; + } + } +} + +/** + * Should only be called when all RGB leds are off + */ +static int rgb_enable_leds(struct rgb_sync *rgb) +{ + struct qpnp_led_data *led; + struct pwm_device *pwm_dev[3]; + int i, rc; + + for (i = 0; i < 3; i++) { + led = rgb->led_data[i]; + if (!led) + continue; + + led->rgb_cfg->pwm_cfg->mode = LPG_MODE; + pwm_free(led->rgb_cfg->pwm_cfg->pwm_dev); + qpnp_pwm_init(led->rgb_cfg->pwm_cfg, led->spmi_dev, led->cdev.name); + pwm_dev[i] = led->rgb_cfg->pwm_cfg->pwm_dev; + } + + if (i == 0) + return 0; + + rc = pwm_enable_synchronized(pwm_dev, i); + if (rc) { + dev_err(&rgb->spmi_dev->dev, "Unable to enable pwms\n"); + return rc; + } + + for (i = 0; i < 3; i++) { + led = rgb->led_data[i]; + if (!led) + continue; + led->rgb_cfg->pwm_cfg->blinking = true; + led->rgb_cfg->pwm_cfg->pwm_enabled = 1; + } + + return rc; +} + +static ssize_t rgb_blink_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rgb_sync *rgb_sync; + struct qpnp_led_data *led; + unsigned long blinking; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + ssize_t rc = -EINVAL, i; + u8 enable = 0; + + rc = kstrtoul(buf, 10, &blinking); + if (rc) + return rc; + rgb_sync = container_of(led_cdev, struct rgb_sync, cdev); + + rgb_lock_leds(rgb_sync); + for (i = 0; i < 3; i++) { + if (rgb_sync->led_data[i]) { + led = rgb_sync->led_data[i]; + enable |= led->rgb_cfg->enable; + } + } + + if (!led) + return count; + + rc = qpnp_led_masked_write(led, + RGB_LED_EN_CTL(led->base), + enable, blinking ? enable : RGB_LED_DISABLE); + if (rc) { + dev_err(&led->spmi_dev->dev, + "Failed to write led enable reg\n"); + rgb_unlock_leds(rgb_sync); + return rc; + } + rgb_disable_leds(rgb_sync); + if (blinking) + rgb_enable_leds(rgb_sync); + rgb_unlock_leds(rgb_sync); + return count; +} + static DEVICE_ATTR(led_mode, 0664, NULL, led_mode_store); static DEVICE_ATTR(strobe, 0664, NULL, led_strobe_type_store); static DEVICE_ATTR(pwm_us, 0664, NULL, pwm_us_store); @@ -2700,6 +2835,7 @@ static DEVICE_ATTR(ramp_step_ms, 0664, NULL, ramp_step_ms_store); static DEVICE_ATTR(lut_flags, 0664, NULL, lut_flags_store); static DEVICE_ATTR(duty_pcts, 0664, NULL, duty_pcts_store); static DEVICE_ATTR(blink, 0664, NULL, blink_store); +static DEVICE_ATTR(rgb_blink, 0664, NULL, rgb_blink_store); static struct attribute *led_attrs[] = { &dev_attr_led_mode.attr, @@ -2731,6 +2867,11 @@ static struct attribute *blink_attrs[] = { NULL }; +static struct attribute *rgb_blink_attrs[] = { + &dev_attr_rgb_blink.attr, + NULL +}; + static const struct attribute_group pwm_attr_group = { .attrs = pwm_attrs, }; @@ -2743,6 +2884,10 @@ static const struct attribute_group blink_attr_group = { .attrs = blink_attrs, }; +static const struct attribute_group rgb_blink_attr_group = { + .attrs = rgb_blink_attrs, +}; + static int qpnp_flash_init(struct qpnp_led_data *led) { int rc; @@ -3861,6 +4006,7 @@ static int qpnp_leds_probe(struct spmi_device *spmi) int rc, i, num_leds = 0, parsed_leds = 0; const char *led_label; bool regulator_probe = false; + struct rgb_sync *rgb_sync = NULL; node = spmi->dev.of_node; if (node == NULL) @@ -3880,6 +4026,29 @@ static int qpnp_leds_probe(struct spmi_device *spmi) return -ENOMEM; } + if (of_property_read_bool(node, "qcom,rgb-sync")) { + rgb_sync = devm_kzalloc(&spmi->dev, + sizeof(struct rgb_sync), GFP_KERNEL); + if (!rgb_sync) { + dev_err(&spmi->dev, "Unable to allocate memory\n"); + kfree(led_array); + return -ENOMEM; + } + rgb_sync->cdev.name = "rgb"; + rgb_sync->spmi_dev = spmi; + rc = led_classdev_register(&spmi->dev, &rgb_sync->cdev); + if (rc) { + dev_err(&spmi->dev, "unable to register rgb %d\n", rc); + goto fail_id_check; + } + rc = sysfs_create_group(&rgb_sync->cdev.dev->kobj, + &rgb_blink_attr_group); + if (rc) { + dev_err(&spmi->dev, "unable to create rgb sysfs %d\n", rc); + goto fail_id_check; + } + } + for_each_child_of_node(node, temp) { led = &led_array[parsed_leds]; led->num_leds = num_leds; @@ -4084,6 +4253,9 @@ static int qpnp_leds_probe(struct spmi_device *spmi) &lpg_attr_group); if (rc) goto fail_id_check; + + if (rgb_sync) + rgb_sync->led_data[QPNP_ID_TO_RGB_IDX(led->id)] = led; } else if (led->rgb_cfg->pwm_cfg->mode == LPG_MODE) { rc = sysfs_create_group(&led->cdev.dev->kobj, &lpg_attr_group); @@ -4130,6 +4302,10 @@ static int qpnp_leds_probe(struct spmi_device *spmi) return 0; fail_id_check: + if (rgb_sync) { + led_classdev_unregister(&rgb_sync->cdev); + kfree(rgb_sync); + } for (i = 0; i < parsed_leds; i++) { if (led_array[i].id != QPNP_ID_FLASH1_LED0 && led_array[i].id != QPNP_ID_FLASH1_LED1) diff --git a/drivers/md/dm-bufio.c b/drivers/md/dm-bufio.c index 38eca8c634eab..775a150a8b4f9 100644 --- a/drivers/md/dm-bufio.c +++ b/drivers/md/dm-bufio.c @@ -756,7 +756,8 @@ static struct dm_buffer *__alloc_buffer_wait_no_callback(struct dm_bufio_client * dm-bufio is resistant to allocation failures (it just keeps * one buffer reserved in cases all the allocations fail). * So set flags to not try too hard: - * GFP_NOIO: don't recurse into the I/O layer + * GFP_NOWAIT: don't wait; if we need to sleep we'll release our + * mutex and wait ourselves. * __GFP_NORETRY: don't retry and rather return failure * __GFP_NOMEMALLOC: don't use emergency reserves * __GFP_NOWARN: don't print a warning in case of failure @@ -766,7 +767,7 @@ static struct dm_buffer *__alloc_buffer_wait_no_callback(struct dm_bufio_client */ while (1) { if (dm_bufio_cache_size_latch != 1) { - b = alloc_buffer(c, GFP_NOIO | __GFP_NORETRY | __GFP_NOMEMALLOC | __GFP_NOWARN); + b = alloc_buffer(c, GFP_NOWAIT | __GFP_NORETRY | __GFP_NOMEMALLOC | __GFP_NOWARN); if (b) return b; } diff --git a/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c b/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c index c19b0d4b61c22..b4616f60dce12 100644 --- a/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c +++ b/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c @@ -991,15 +991,19 @@ static int msm_fd_s_ctrl(struct file *file, void *fh, struct v4l2_control *a) a->value = ctx->format.size->work_size; break; case V4L2_CID_FD_WORK_MEMORY_FD: + mutex_lock(&ctx->fd_device->recovery_lock); if (ctx->work_buf.handle) msm_fd_hw_unmap_buffer(&ctx->work_buf); if (a->value >= 0) { ret = msm_fd_hw_map_buffer(&ctx->mem_pool, a->value, &ctx->work_buf); - if (ret < 0) + if (ret < 0) { + mutex_unlock(&ctx->fd_device->recovery_lock); return ret; + } } + mutex_unlock(&ctx->fd_device->recovery_lock); break; default: return -EINVAL; diff --git a/drivers/media/platform/msm/camera_v2/sensor/actuator/msm_actuator.c b/drivers/media/platform/msm/camera_v2/sensor/actuator/msm_actuator.c old mode 100755 new mode 100644 index cde09aff4f2f1..e3c81f3396bfa --- a/drivers/media/platform/msm/camera_v2/sensor/actuator/msm_actuator.c +++ b/drivers/media/platform/msm/camera_v2/sensor/actuator/msm_actuator.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2015, 2017 The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2015, 2018 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -98,6 +98,11 @@ static int32_t msm_actuator_piezo_set_default_focus( struct msm_camera_i2c_reg_setting reg_setting; CDBG("Enter\n"); + if (a_ctrl->i2c_reg_tbl == NULL) { + pr_err("failed. i2c reg tabl is NULL"); + return -EFAULT; + } + if (a_ctrl->curr_step_pos != 0) { a_ctrl->i2c_tbl_index = 0; a_ctrl->func_tbl->actuator_parse_i2c_params(a_ctrl, @@ -125,21 +130,18 @@ static int32_t msm_actuator_piezo_set_default_focus( static void msm_actuator_parse_i2c_params(struct msm_actuator_ctrl_t *a_ctrl, int16_t next_lens_position, uint32_t hw_params, uint16_t delay) { - struct msm_actuator_reg_params_t *write_arr = a_ctrl->reg_tbl; + struct msm_actuator_reg_params_t *write_arr = NULL; uint32_t hw_dword = hw_params; uint16_t i2c_byte1 = 0, i2c_byte2 = 0; uint16_t value = 0; - uint32_t size = a_ctrl->reg_tbl_size, i = 0; - struct msm_camera_i2c_reg_array *i2c_tbl = a_ctrl->i2c_reg_tbl; + uint32_t size = 0, i = 0; + struct msm_camera_i2c_reg_array *i2c_tbl = NULL; CDBG("Enter\n"); - if (a_ctrl == NULL) { - pr_err("failed. actuator ctrl is NULL"); - return; - } - - if (a_ctrl->i2c_reg_tbl == NULL) { - pr_err("failed. i2c reg tabl is NULL"); + if ((!a_ctrl) || + (!a_ctrl->reg_tbl) || + (!a_ctrl->i2c_reg_tbl)) { + pr_err("failed. NULL actuator pointers"); return; } @@ -617,6 +619,11 @@ static int32_t msm_actuator_piezo_move_focus( return -EFAULT; } + if (a_ctrl->i2c_reg_tbl == NULL) { + pr_err("failed. i2c reg tabl is NULL"); + return -EFAULT; + } + if (dest_step_position > a_ctrl->total_steps) { pr_err("Step pos greater than total steps = %d\n", dest_step_position); @@ -687,6 +694,10 @@ static int32_t msm_actuator_move_focus( pr_err("Invalid direction = %d\n", dir); return -EFAULT; } + if (a_ctrl->i2c_reg_tbl == NULL) { + pr_err("failed. i2c reg tabl is NULL"); + return -EFAULT; + } if (dest_step_pos > a_ctrl->total_steps) { pr_err("Step pos greater than total steps = %d\n", dest_step_pos); @@ -698,6 +709,8 @@ static int32_t msm_actuator_move_focus( a_ctrl->curr_step_pos, dest_step_pos, curr_lens_pos); while (a_ctrl->curr_step_pos != dest_step_pos) { + if (a_ctrl->curr_region_index >= a_ctrl->region_size) + break; step_boundary = a_ctrl->region_params[a_ctrl->curr_region_index]. step_bound[dir]; @@ -808,6 +821,10 @@ static int32_t msm_actuator_bivcm_move_focus( pr_err("Invalid direction = %d\n", dir); return -EFAULT; } + if (a_ctrl->i2c_reg_tbl == NULL) { + pr_err("failed. i2c reg tabl is NULL"); + return -EFAULT; + } if (dest_step_pos > a_ctrl->total_steps) { pr_err("Step pos greater than total steps = %d\n", dest_step_pos); @@ -819,6 +836,8 @@ static int32_t msm_actuator_bivcm_move_focus( a_ctrl->curr_step_pos, dest_step_pos, curr_lens_pos); while (a_ctrl->curr_step_pos != dest_step_pos) { + if (a_ctrl->curr_region_index >= a_ctrl->region_size) + break; step_boundary = a_ctrl->region_params[a_ctrl->curr_region_index]. step_bound[dir]; @@ -1206,6 +1225,18 @@ static int32_t msm_actuator_set_position( return -EFAULT; } + if (!a_ctrl || !a_ctrl->func_tbl || + !a_ctrl->func_tbl->actuator_parse_i2c_params || + !a_ctrl->i2c_reg_tbl) { + pr_err("failed. NULL actuator pointers."); + return -EFAULT; + } + + if (a_ctrl->actuator_state != ACT_OPS_ACTIVE) { + pr_err("failed. Invalid actuator state."); + return -EFAULT; + } + a_ctrl->i2c_tbl_index = 0; for (index = 0; index < set_pos->number_of_steps; index++) { next_lens_position = set_pos->pos[index]; @@ -1325,12 +1356,10 @@ static int32_t msm_actuator_set_param(struct msm_actuator_ctrl_t *a_ctrl, a_ctrl->region_size = set_info->af_tuning_params.region_size; a_ctrl->pwd_step = set_info->af_tuning_params.pwd_step; - a_ctrl->total_steps = set_info->af_tuning_params.total_steps; if (copy_from_user(&a_ctrl->region_params, (void *)set_info->af_tuning_params.region_params, a_ctrl->region_size * sizeof(struct region_params_t))) { - a_ctrl->total_steps = 0; pr_err("Error copying region_params\n"); return -EFAULT; } @@ -1361,6 +1390,7 @@ static int32_t msm_actuator_set_param(struct msm_actuator_ctrl_t *a_ctrl, (a_ctrl->i2c_reg_tbl != NULL)) { kfree(a_ctrl->i2c_reg_tbl); } + a_ctrl->i2c_reg_tbl = NULL; a_ctrl->i2c_reg_tbl = kzalloc(sizeof(struct msm_camera_i2c_reg_array) * @@ -1370,6 +1400,8 @@ static int32_t msm_actuator_set_param(struct msm_actuator_ctrl_t *a_ctrl, return -ENOMEM; } + a_ctrl->total_steps = set_info->af_tuning_params.total_steps; + if (copy_from_user(&a_ctrl->reg_tbl, (void *)set_info->actuator_params.reg_tbl_params, a_ctrl->reg_tbl_size * diff --git a/drivers/media/platform/msm/vidc/hfi_packetization.c b/drivers/media/platform/msm/vidc/hfi_packetization.c index 4f1827a2b727b..7808497508c5a 100644 --- a/drivers/media/platform/msm/vidc/hfi_packetization.c +++ b/drivers/media/platform/msm/vidc/hfi_packetization.c @@ -493,9 +493,6 @@ static int get_hfi_extradata_index(enum hal_extradata_id index) case HAL_EXTRADATA_METADATA_MBI: ret = HFI_PROPERTY_PARAM_VENC_MBI_DUMPING; break; - case HAL_EXTRADATA_VUI_DISPLAY_INFO: - ret = HFI_PROPERTY_PARAM_VUI_DISPLAY_INFO_EXTRADATA; - break; default: dprintk(VIDC_WARN, "Extradata index not found: %d\n", index); break; @@ -1863,7 +1860,6 @@ int create_pkt_cmd_session_set_property( sizeof(struct hfi_hybrid_hierp); break; } - case HAL_PARAM_VENC_CONSTRAINED_INTRA_PRED: { create_pkt_enable(pkt->rg_property_data, @@ -1872,44 +1868,6 @@ int create_pkt_cmd_session_set_property( pkt->size += sizeof(u32) + sizeof(struct hfi_enable); break; } - - case HAL_PARAM_VENC_VIDEO_SIGNAL_INFO: - { - u32 color_space, matrix_coeffs, transfer_chars; - struct hal_video_signal_info *hal = pdata; - struct hfi_video_signal_metadata *signal_info = - (struct hfi_video_signal_metadata *) - &pkt->rg_property_data[1]; - - switch (hal->color_space) { - /* See colour_primaries of ISO/IEC 14496 for significance */ - case HAL_VIDEO_COLOR_SPACE_601: - color_space = 5; - transfer_chars = 6; - matrix_coeffs = 5; - break; - case HAL_VIDEO_COLOR_SPACE_709: - color_space = 1; - transfer_chars = 1; - matrix_coeffs = 1; - break; - default: - return -ENOTSUPP; - } - - signal_info->enable = true; - signal_info->video_format = 5; - signal_info->video_full_range = !hal->clamped; - signal_info->color_description = 1; - signal_info->color_primaries = color_space; - signal_info->transfer_characteristics = transfer_chars; - signal_info->matrix_coeffs = matrix_coeffs; - - pkt->rg_property_data[0] = - HFI_PROPERTY_PARAM_VENC_VIDEO_SIGNAL_INFO; - pkt->size += sizeof(u32) + sizeof(*signal_info); - break; - } /* FOLLOWING PROPERTIES ARE NOT IMPLEMENTED IN CORE YET */ case HAL_CONFIG_BUFFER_REQUIREMENTS: case HAL_CONFIG_PRIORITY: diff --git a/drivers/media/platform/msm/vidc/msm_vdec.c b/drivers/media/platform/msm/vidc/msm_vdec.c index db6ae229c687c..d9327740a7391 100644 --- a/drivers/media/platform/msm/vidc/msm_vdec.c +++ b/drivers/media/platform/msm/vidc/msm_vdec.c @@ -62,6 +62,8 @@ static const char *const mpeg_video_vidc_extradata[] = { "Extradata Frame Rate", "Extradata Panscan Window", "Extradata Recovery point SEI", + "Extradata Closed Caption UD", + "Extradata AFD UD", "Extradata Multislice info", "Extradata number of concealed MB", "Extradata metadata filler", @@ -69,10 +71,6 @@ static const char *const mpeg_video_vidc_extradata[] = { "Extradata digital zoom", "Extradata aspect ratio", "Extradata mpeg2 seqdisp", - "Extradata UD", - "Extradata frame QP", - "Extradata frame bits info", - "Extradata display VUI", }; static const char *const mpeg_vidc_video_alloc_mode_type[] = { "Buffer Allocation Static", @@ -249,7 +247,7 @@ static struct msm_vidc_ctrl msm_vdec_ctrls[] = { .name = "Extradata Type", .type = V4L2_CTRL_TYPE_MENU, .minimum = V4L2_MPEG_VIDC_EXTRADATA_NONE, - .maximum = V4L2_MPEG_VIDC_EXTRADATA_VUI_DISPLAY, + .maximum = V4L2_MPEG_VIDC_EXTRADATA_FRAME_BITS_INFO, .default_value = V4L2_MPEG_VIDC_EXTRADATA_NONE, .menu_skip_mask = ~( (1 << V4L2_MPEG_VIDC_EXTRADATA_NONE) | @@ -271,8 +269,7 @@ static struct msm_vidc_ctrl msm_vdec_ctrls[] = { (1 << V4L2_MPEG_VIDC_EXTRADATA_MPEG2_SEQDISP) | (1 << V4L2_MPEG_VIDC_EXTRADATA_STREAM_USERDATA) | (1 << V4L2_MPEG_VIDC_EXTRADATA_FRAME_QP) | - (1 << V4L2_MPEG_VIDC_EXTRADATA_FRAME_BITS_INFO) | - (1 << V4L2_MPEG_VIDC_EXTRADATA_VUI_DISPLAY) + (1 << V4L2_MPEG_VIDC_EXTRADATA_FRAME_BITS_INFO) ), .qmenu = mpeg_video_vidc_extradata, .step = 0, diff --git a/drivers/media/platform/msm/vidc/msm_venc.c b/drivers/media/platform/msm/vidc/msm_venc.c index 4c818877af218..a694fe6dd3c14 100644 --- a/drivers/media/platform/msm/vidc/msm_venc.c +++ b/drivers/media/platform/msm/vidc/msm_venc.c @@ -3202,7 +3202,6 @@ int msm_venc_s_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { struct hal_uncompressed_format_select hal_fmt = {0}; struct hal_frame_size frame_sz; - struct hal_video_signal_info signal_info = {0}; inst->prop.width[OUTPUT_PORT] = f->fmt.pix_mp.width; inst->prop.height[OUTPUT_PORT] = f->fmt.pix_mp.height; @@ -3214,7 +3213,6 @@ int msm_venc_s_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) goto exit; } - /* Configure frame dimensions */ frame_sz.buffer_type = HAL_BUFFER_INPUT; frame_sz.width = inst->prop.width[OUTPUT_PORT]; frame_sz.height = inst->prop.height[OUTPUT_PORT]; @@ -3237,7 +3235,6 @@ int msm_venc_s_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) goto exit; } - /* Configure frame color format */ fmt = msm_comm_get_pixel_fmt_fourcc(venc_formats, ARRAY_SIZE(venc_formats), f->fmt.pix_mp.pixelformat, OUTPUT_PORT); @@ -3271,54 +3268,6 @@ int msm_venc_s_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) "Failed to set input color format\n"); goto exit; } - - /* Configure frame color format characteristics */ - if (f->fmt.pix_mp.colorspace) { - switch (f->fmt.pix_mp.colorspace) { - case V4L2_COLORSPACE_REC709: - signal_info.color_space = - HAL_VIDEO_COLOR_SPACE_709; - signal_info.clamped = true; - break; - case V4L2_COLORSPACE_BT878: - /* equiv to ITU-R BT.601 clamped */ - signal_info.clamped = true; - /* fall thru */ - case V4L2_COLORSPACE_470_SYSTEM_BG: - /* equiv to ITU-R BT.601 */ - signal_info.color_space = - HAL_VIDEO_COLOR_SPACE_601; - break; - default: - dprintk(VIDC_ERR, "Colorspace %d not supported\n", - f->fmt.pix_mp.colorspace); - rc = -ENOTSUPP; - goto exit; - } - - switch (f->fmt.pix_mp.pixelformat) { - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - rc = call_hfi_op(hdev, session_set_property, - inst->session, - HAL_PARAM_VENC_VIDEO_SIGNAL_INFO, - &signal_info); - if (rc) { - dprintk(VIDC_ERR, - "Failed to set the colorspace: %d\n", - rc); - goto exit; - } - break; - default: - dprintk(VIDC_ERR, - "Colorspace %d not supported for format %d\n", - f->fmt.pix_mp.colorspace, - f->fmt.pix_mp.pixelformat); - rc = -ENOTSUPP; - break; - } - } } if (!fmt) { @@ -3350,8 +3299,9 @@ int msm_venc_s_fmt(struct msm_vidc_inst *inst, struct v4l2_format *f) frame_sz.width = inst->prop.width[CAPTURE_PORT]; frame_sz.height = inst->prop.height[CAPTURE_PORT]; frame_sz.buffer_type = HAL_BUFFER_OUTPUT; - rc = call_hfi_op(hdev, session_set_property, inst->session, - HAL_PARAM_FRAME_SIZE, &frame_sz); + rc = call_hfi_op(hdev, session_set_property, (void *) + inst->session, HAL_PARAM_FRAME_SIZE, + &frame_sz); if (rc) { dprintk(VIDC_ERR, "Failed to set OUTPUT framesize\n"); diff --git a/drivers/media/platform/msm/vidc/msm_vidc_common.c b/drivers/media/platform/msm/vidc/msm_vidc_common.c index 3eaa3ba26736c..40dd791bdff65 100644 --- a/drivers/media/platform/msm/vidc/msm_vidc_common.c +++ b/drivers/media/platform/msm/vidc/msm_vidc_common.c @@ -3947,9 +3947,6 @@ enum hal_extradata_id msm_comm_get_hal_extradata_index( case V4L2_MPEG_VIDC_EXTRADATA_METADATA_MBI: ret = HAL_EXTRADATA_METADATA_MBI; break; - case V4L2_MPEG_VIDC_EXTRADATA_VUI_DISPLAY: - ret = HAL_EXTRADATA_VUI_DISPLAY_INFO; - break; default: dprintk(VIDC_WARN, "Extradata not found: %d\n", index); break; diff --git a/drivers/media/platform/msm/vidc/vidc_hfi.h b/drivers/media/platform/msm/vidc/vidc_hfi.h index 5c9c4b2a9bfa8..8eefc7cee6160 100644 --- a/drivers/media/platform/msm/vidc/vidc_hfi.h +++ b/drivers/media/platform/msm/vidc/vidc_hfi.h @@ -212,8 +212,6 @@ struct hfi_extradata_header { (HFI_PROPERTY_PARAM_VDEC_OX_START + 0x019) #define HFI_PROPERTY_PARAM_VDEC_SCS_THRESHOLD \ (HFI_PROPERTY_PARAM_VDEC_OX_START + 0x01A) -#define HFI_PROPERTY_PARAM_VUI_DISPLAY_INFO_EXTRADATA \ - (HFI_PROPERTY_PARAM_VDEC_OX_START + 0x01B) #define HFI_PROPERTY_CONFIG_VDEC_OX_START \ (HFI_DOMAIN_BASE_VDEC + HFI_ARCH_OX_OFFSET + 0x0000) diff --git a/drivers/media/platform/msm/vidc/vidc_hfi_api.h b/drivers/media/platform/msm/vidc/vidc_hfi_api.h index 482809de3ebe5..eda66d35de98b 100644 --- a/drivers/media/platform/msm/vidc/vidc_hfi_api.h +++ b/drivers/media/platform/msm/vidc/vidc_hfi_api.h @@ -114,7 +114,6 @@ enum hal_extradata_id { HAL_EXTRADATA_DIGITAL_ZOOM, HAL_EXTRADATA_LTR_INFO, HAL_EXTRADATA_METADATA_MBI, - HAL_EXTRADATA_VUI_DISPLAY_INFO, }; enum hal_property { @@ -216,7 +215,6 @@ enum hal_property { HAL_PARAM_VDEC_NON_SECURE_OUTPUT2, HAL_PARAM_VENC_HIER_P_HYBRID_MODE, HAL_PARAM_VENC_CONSTRAINED_INTRA_PRED, - HAL_PARAM_VENC_VIDEO_SIGNAL_INFO, }; enum hal_domain { @@ -927,16 +925,6 @@ struct hal_vpe_color_space_conversion { u32 csc_limit[HAL_MAX_LIMIT_COEFFS]; }; -enum hal_video_color_space { - HAL_VIDEO_COLOR_SPACE_601, - HAL_VIDEO_COLOR_SPACE_709, -}; - -struct hal_video_signal_info { - enum hal_video_color_space color_space; - bool clamped; -}; - enum vidc_resource_id { VIDC_RESOURCE_OCMEM = 0x00000001, VIDC_UNUSED_RESORUCE = 0x10000000, diff --git a/drivers/media/platform/msm/vidc/vidc_hfi_helper.h b/drivers/media/platform/msm/vidc/vidc_hfi_helper.h index 13ed39410a660..2c60d0e9d04e9 100644 --- a/drivers/media/platform/msm/vidc/vidc_hfi_helper.h +++ b/drivers/media/platform/msm/vidc/vidc_hfi_helper.h @@ -338,7 +338,7 @@ struct hfi_buffer_info { (HFI_PROPERTY_PARAM_VENC_COMMON_START + 0x01B) #define HFI_PROPERTY_PARAM_VENC_LTRMODE \ (HFI_PROPERTY_PARAM_VENC_COMMON_START + 0x01C) -#define HFI_PROPERTY_PARAM_VENC_VIDEO_SIGNAL_INFO \ +#define HFI_PROPERTY_PARAM_VENC_VIDEO_FULL_RANGE \ (HFI_PROPERTY_PARAM_VENC_COMMON_START + 0x01D) #define HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO \ (HFI_PROPERTY_PARAM_VENC_COMMON_START + 0x01E) @@ -628,16 +628,6 @@ struct hfi_frame_size { u32 height; }; -struct hfi_video_signal_metadata { - u32 enable; - u32 video_format; - u32 video_full_range; - u32 color_description; - u32 color_primaries; - u32 transfer_characteristics; - u32 matrix_coeffs; -}; - struct hfi_h264_vui_timing_info { u32 enable; u32 fixed_frame_rate; diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index ff1f282a734e4..88b808f346416 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -541,11 +541,12 @@ config SRAM the genalloc API. It is supposed to be used for small on-chip SRAM areas found on many SoCs. -config UID_CPUTIME - tristate "Per-UID cpu time statistics" - depends on PROFILING +config UID_SYS_STATS + bool "Per-UID statistics" + depends on PROFILING && TASK_XACCT && TASK_IO_ACCOUNTING help Per UID based cpu time statistics exported to /proc/uid_cputime + Per UID based io statistics exported to /proc/uid_io config TSIF depends on ARCH_MSM8X60 || ARCH_MSM8960 || ARCH_APQ8064 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 94e0fe34ae72f..e873f9bae1953 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -56,7 +56,6 @@ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o obj-$(CONFIG_SRAM) += sram.o -obj-$(CONFIG_UID_CPUTIME) += uid_cputime.o obj-$(CONFIG_TSIF) += msm_tsif.o obj-$(CONFIG_HAPTIC_ISA1200) += isa1200.o obj-$(CONFIG_QSEECOM) += qseecom.o @@ -72,4 +71,4 @@ obj-y += qcom/ #shankai@bsp, 2015/2/2, Add for :drv2605 kernel support obj-$(CONFIG_TI_DRV2605) += ti_drv2605.o #endif VENDOR_EDIT - +obj-$(CONFIG_UID_SYS_STATS) += uid_sys_stats.o diff --git a/drivers/misc/qseecom.c b/drivers/misc/qseecom.c index 01732976d44f6..1d38f4d7e41aa 100644 --- a/drivers/misc/qseecom.c +++ b/drivers/misc/qseecom.c @@ -1,6 +1,6 @@ /*Qualcomm Secure Execution Environment Communicator (QSEECOM) driver * - * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -3448,6 +3448,23 @@ static int qseecom_get_qseos_version(struct qseecom_dev_handle *data, return 0; } +static int qseecom_get_qsee_version(struct qseecom_dev_handle *data, + void __user *argp) +{ + struct qseecom_qsee_version_req req; + + if (copy_from_user(&req, argp, sizeof(req))) { + pr_err("copy_from_user failed\n"); + return -EINVAL; + } + req.qsee_version = qseecom.qsee_version; + if (copy_to_user(argp, &req, sizeof(req))) { + pr_err("copy_to_user failed\n"); + return -EINVAL; + } + return 0; +} + static int __qseecom_enable_clk(enum qseecom_ce_hw_instance ce) { int rc = 0; @@ -5312,6 +5329,14 @@ long qseecom_ioctl(struct file *file, unsigned cmd, unsigned long arg) atomic_dec(&data->ioctl_count); break; } + case QSEECOM_IOCTL_GET_QSEE_VERSION_REQ: { + atomic_inc(&data->ioctl_count); + ret = qseecom_get_qsee_version(data, argp); + if (ret) + pr_err("qseecom_get_qseos_version: %d\n", ret); + atomic_dec(&data->ioctl_count); + break; + } case QSEECOM_IOCTL_PERF_ENABLE_REQ:{ if ((data->type != QSEECOM_GENERIC) && (data->type != QSEECOM_CLIENT_APP)) { diff --git a/drivers/misc/uid_cputime.c b/drivers/misc/uid_cputime.c deleted file mode 100644 index c751188ce2aba..0000000000000 --- a/drivers/misc/uid_cputime.c +++ /dev/null @@ -1,254 +0,0 @@ -/* drivers/misc/uid_cputime.c - * - * Copyright (C) 2014 - 2015 Google, Inc. - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define UID_HASH_BITS 10 -DECLARE_HASHTABLE(hash_table, UID_HASH_BITS); - -static DEFINE_MUTEX(uid_lock); -static struct proc_dir_entry *parent; - -struct uid_entry { - uid_t uid; - cputime_t utime; - cputime_t stime; - cputime_t active_utime; - cputime_t active_stime; - unsigned long long active_power; - unsigned long long power; - struct hlist_node hash; -}; - -static struct uid_entry *find_uid_entry(uid_t uid) -{ - struct uid_entry *uid_entry; - hash_for_each_possible(hash_table, uid_entry, hash, uid) { - if (uid_entry->uid == uid) - return uid_entry; - } - return NULL; -} - -static struct uid_entry *find_or_register_uid(uid_t uid) -{ - struct uid_entry *uid_entry; - - uid_entry = find_uid_entry(uid); - if (uid_entry) - return uid_entry; - - uid_entry = kzalloc(sizeof(struct uid_entry), GFP_ATOMIC); - if (!uid_entry) - return NULL; - - uid_entry->uid = uid; - - hash_add(hash_table, &uid_entry->hash, uid); - - return uid_entry; -} - -static int uid_stat_show(struct seq_file *m, void *v) -{ - struct uid_entry *uid_entry; - struct task_struct *task, *temp; - cputime_t utime; - cputime_t stime; - unsigned long bkt; - - mutex_lock(&uid_lock); - - hash_for_each(hash_table, bkt, uid_entry, hash) { - uid_entry->active_stime = 0; - uid_entry->active_utime = 0; - uid_entry->active_power = 0; - } - - read_lock(&tasklist_lock); - do_each_thread(temp, task) { - uid_entry = find_or_register_uid(from_kuid_munged( - current_user_ns(), task_uid(task))); - if (!uid_entry) { - read_unlock(&tasklist_lock); - mutex_unlock(&uid_lock); - pr_err("%s: failed to find the uid_entry for uid %d\n", - __func__, from_kuid_munged(current_user_ns(), - task_uid(task))); - return -ENOMEM; - } - /* if this task is exiting, we have already accounted for the - * time and power. - */ - if (task->cpu_power == ULLONG_MAX) - continue; - task_cputime_adjusted(task, &utime, &stime); - uid_entry->active_utime += utime; - uid_entry->active_stime += stime; - uid_entry->active_power += task->cpu_power; - } while_each_thread(temp, task); - read_unlock(&tasklist_lock); - - hash_for_each(hash_table, bkt, uid_entry, hash) { - cputime_t total_utime = uid_entry->utime + - uid_entry->active_utime; - cputime_t total_stime = uid_entry->stime + - uid_entry->active_stime; - unsigned long long total_power = uid_entry->power + - uid_entry->active_power; - seq_printf(m, "%d: %llu %llu %llu\n", uid_entry->uid, - (unsigned long long)jiffies_to_msecs( - cputime_to_jiffies(total_utime)) * USEC_PER_MSEC, - (unsigned long long)jiffies_to_msecs( - cputime_to_jiffies(total_stime)) * USEC_PER_MSEC, - total_power); - } - - mutex_unlock(&uid_lock); - return 0; -} - -static int uid_stat_open(struct inode *inode, struct file *file) -{ - return single_open(file, uid_stat_show, PDE_DATA(inode)); -} - -static const struct file_operations uid_stat_fops = { - .open = uid_stat_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int uid_remove_open(struct inode *inode, struct file *file) -{ - return single_open(file, NULL, NULL); -} - -static ssize_t uid_remove_write(struct file *file, - const char __user *buffer, size_t count, loff_t *ppos) -{ - struct uid_entry *uid_entry; - struct hlist_node *tmp; - char uids[128]; - char *start_uid, *end_uid = NULL; - long int uid_start = 0, uid_end = 0; - - if (count >= sizeof(uids)) - count = sizeof(uids) - 1; - - if (copy_from_user(uids, buffer, count)) - return -EFAULT; - - uids[count] = '\0'; - end_uid = uids; - start_uid = strsep(&end_uid, "-"); - - if (!start_uid || !end_uid) - return -EINVAL; - - if (kstrtol(start_uid, 10, &uid_start) != 0 || - kstrtol(end_uid, 10, &uid_end) != 0) { - return -EINVAL; - } - mutex_lock(&uid_lock); - - for (; uid_start <= uid_end; uid_start++) { - hash_for_each_possible_safe(hash_table, uid_entry, tmp, - hash, (uid_t)uid_start) { - if (uid_start == uid_entry->uid) { - hash_del(&uid_entry->hash); - kfree(uid_entry); - } - } - } - - mutex_unlock(&uid_lock); - return count; -} - -static const struct file_operations uid_remove_fops = { - .open = uid_remove_open, - .release = single_release, - .write = uid_remove_write, -}; - -static int process_notifier(struct notifier_block *self, - unsigned long cmd, void *v) -{ - struct task_struct *task = v; - struct uid_entry *uid_entry; - cputime_t utime, stime; - uid_t uid; - - if (!task) - return NOTIFY_OK; - - mutex_lock(&uid_lock); - uid = from_kuid_munged(current_user_ns(), task_uid(task)); - uid_entry = find_or_register_uid(uid); - if (!uid_entry) { - pr_err("%s: failed to find uid %d\n", __func__, uid); - goto exit; - } - - task_cputime_adjusted(task, &utime, &stime); - uid_entry->utime += utime; - uid_entry->stime += stime; - uid_entry->power += task->cpu_power; - task->cpu_power = ULLONG_MAX; - -exit: - mutex_unlock(&uid_lock); - return NOTIFY_OK; -} - -static struct notifier_block process_notifier_block = { - .notifier_call = process_notifier, -}; - -static int __init proc_uid_cputime_init(void) -{ - hash_init(hash_table); - - parent = proc_mkdir("uid_cputime", NULL); - if (!parent) { - pr_err("%s: failed to create proc entry\n", __func__); - return -ENOMEM; - } - - proc_create_data("remove_uid_range", S_IWUGO, parent, &uid_remove_fops, - NULL); - - proc_create_data("show_uid_stat", S_IRUGO, parent, &uid_stat_fops, - NULL); - - profile_event_register(PROFILE_TASK_EXIT, &process_notifier_block); - - return 0; -} - -early_initcall(proc_uid_cputime_init); diff --git a/drivers/misc/uid_sys_stats.c b/drivers/misc/uid_sys_stats.c new file mode 100644 index 0000000000000..2aa7246700b16 --- /dev/null +++ b/drivers/misc/uid_sys_stats.c @@ -0,0 +1,512 @@ +/* drivers/misc/uid_sys_stats.c + * + * Copyright (C) 2014 - 2015 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UID_HASH_BITS 10 +DECLARE_HASHTABLE(hash_table, UID_HASH_BITS); + +static DEFINE_RT_MUTEX(uid_lock); +static struct proc_dir_entry *cpu_parent; +static struct proc_dir_entry *io_parent; +static struct proc_dir_entry *proc_parent; + +struct io_stats { + u64 read_bytes; + u64 write_bytes; + u64 rchar; + u64 wchar; + u64 fsync; +}; + +#define UID_STATE_FOREGROUND 0 +#define UID_STATE_BACKGROUND 1 +#define UID_STATE_BUCKET_SIZE 2 + +#define UID_STATE_TOTAL_CURR 2 +#define UID_STATE_TOTAL_LAST 3 +#define UID_STATE_DEAD_TASKS 4 +#define UID_STATE_SIZE 5 + +struct uid_entry { + uid_t uid; + cputime_t utime; + cputime_t stime; + cputime_t active_utime; + cputime_t active_stime; + unsigned long long active_power; + unsigned long long power; + int state; + struct io_stats io[UID_STATE_SIZE]; + struct hlist_node hash; +}; + +static struct uid_entry *find_uid_entry(uid_t uid) +{ + struct uid_entry *uid_entry; + hash_for_each_possible(hash_table, uid_entry, hash, uid) { + if (uid_entry->uid == uid) + return uid_entry; + } + return NULL; +} + +static struct uid_entry *find_or_register_uid(uid_t uid) +{ + struct uid_entry *uid_entry; + + uid_entry = find_uid_entry(uid); + if (uid_entry) + return uid_entry; + + uid_entry = kzalloc(sizeof(struct uid_entry), GFP_ATOMIC); + if (!uid_entry) + return NULL; + + uid_entry->uid = uid; + + hash_add(hash_table, &uid_entry->hash, uid); + + return uid_entry; +} + +static int uid_cputime_show(struct seq_file *m, void *v) +{ + struct uid_entry *uid_entry; + struct task_struct *task, *temp; + struct user_namespace *user_ns = current_user_ns(); + cputime_t utime; + cputime_t stime; + unsigned long bkt; + uid_t uid; + + rt_mutex_lock(&uid_lock); + + hash_for_each(hash_table, bkt, uid_entry, hash) { + uid_entry->active_stime = 0; + uid_entry->active_utime = 0; + uid_entry->active_power = 0; + } + + read_lock(&tasklist_lock); + do_each_thread(temp, task) { + uid = from_kuid_munged(user_ns, task_uid(task)); + uid_entry = find_or_register_uid(uid); + if (!uid_entry) { + read_unlock(&tasklist_lock); + rt_mutex_unlock(&uid_lock); + pr_err("%s: failed to find the uid_entry for uid %d\n", + __func__, uid); + return -ENOMEM; + } + /* if this task is exiting, we have already accounted for the + * time and power. + */ + if (task->cpu_power == ULLONG_MAX) + continue; + task_cputime_adjusted(task, &utime, &stime); + uid_entry->active_utime += utime; + uid_entry->active_stime += stime; + uid_entry->active_power += task->cpu_power; + } while_each_thread(temp, task); + read_unlock(&tasklist_lock); + + hash_for_each(hash_table, bkt, uid_entry, hash) { + cputime_t total_utime = uid_entry->utime + + uid_entry->active_utime; + cputime_t total_stime = uid_entry->stime + + uid_entry->active_stime; + unsigned long long total_power = uid_entry->power + + uid_entry->active_power; + seq_printf(m, "%d: %llu %llu %llu\n", uid_entry->uid, + (unsigned long long)jiffies_to_msecs( + cputime_to_jiffies(total_utime)) * USEC_PER_MSEC, + (unsigned long long)jiffies_to_msecs( + cputime_to_jiffies(total_stime)) * USEC_PER_MSEC, + total_power); + } + + rt_mutex_unlock(&uid_lock); + return 0; +} + +static int uid_cputime_open(struct inode *inode, struct file *file) +{ + return single_open(file, uid_cputime_show, PDE_DATA(inode)); +} + +static const struct file_operations uid_cputime_fops = { + .open = uid_cputime_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int uid_remove_open(struct inode *inode, struct file *file) +{ + return single_open(file, NULL, NULL); +} + +static ssize_t uid_remove_write(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + struct uid_entry *uid_entry; + struct hlist_node *tmp; + char uids[128]; + char *start_uid, *end_uid = NULL; + long int uid_start = 0, uid_end = 0; + + if (count >= sizeof(uids)) + count = sizeof(uids) - 1; + + if (copy_from_user(uids, buffer, count)) + return -EFAULT; + + uids[count] = '\0'; + end_uid = uids; + start_uid = strsep(&end_uid, "-"); + + if (!start_uid || !end_uid) + return -EINVAL; + + if (kstrtol(start_uid, 10, &uid_start) != 0 || + kstrtol(end_uid, 10, &uid_end) != 0) { + return -EINVAL; + } + rt_mutex_lock(&uid_lock); + + for (; uid_start <= uid_end; uid_start++) { + hash_for_each_possible_safe(hash_table, uid_entry, tmp, + hash, (uid_t)uid_start) { + if (uid_start == uid_entry->uid) { + hash_del(&uid_entry->hash); + kfree(uid_entry); + } + } + } + + rt_mutex_unlock(&uid_lock); + return count; +} + +static const struct file_operations uid_remove_fops = { + .open = uid_remove_open, + .release = single_release, + .write = uid_remove_write, +}; + +static u64 compute_write_bytes(struct task_struct *task) +{ + if (task->ioac.write_bytes <= task->ioac.cancelled_write_bytes) + return 0; + + return task->ioac.write_bytes - task->ioac.cancelled_write_bytes; +} + +static void add_uid_io_stats(struct uid_entry *uid_entry, + struct task_struct *task, int slot) +{ + struct io_stats *io_slot = &uid_entry->io[slot]; + + io_slot->read_bytes += task->ioac.read_bytes; + io_slot->write_bytes += compute_write_bytes(task); + io_slot->rchar += task->ioac.rchar; + io_slot->wchar += task->ioac.wchar; + io_slot->fsync += task->ioac.syscfs; +} + +static void compute_uid_io_bucket_stats(struct io_stats *io_bucket, + struct io_stats *io_curr, + struct io_stats *io_last, + struct io_stats *io_dead) +{ + s64 delta; + + delta = io_curr->read_bytes + io_dead->read_bytes - + io_last->read_bytes; + if (delta > 0) + io_bucket->read_bytes += delta; + + delta = io_curr->write_bytes + io_dead->write_bytes - + io_last->write_bytes; + if (delta > 0) + io_bucket->write_bytes += delta; + + delta = io_curr->rchar + io_dead->rchar - io_last->rchar; + if (delta > 0) + io_bucket->rchar += delta; + + delta = io_curr->wchar + io_dead->wchar - io_last->wchar; + if (delta > 0) + io_bucket->wchar += delta; + + delta = io_curr->fsync + io_dead->fsync - io_last->fsync; + if (delta > 0) + io_bucket->fsync += delta; + + io_last->read_bytes = io_curr->read_bytes; + io_last->write_bytes = io_curr->write_bytes; + io_last->rchar = io_curr->rchar; + io_last->wchar = io_curr->wchar; + io_last->fsync = io_curr->fsync; + + memset(io_dead, 0, sizeof(struct io_stats)); +} + +static void update_io_stats_all_locked(void) +{ + struct uid_entry *uid_entry; + struct task_struct *task, *temp; + struct user_namespace *user_ns = current_user_ns(); + unsigned long bkt; + uid_t uid; + + hash_for_each(hash_table, bkt, uid_entry, hash) + memset(&uid_entry->io[UID_STATE_TOTAL_CURR], 0, + sizeof(struct io_stats)); + + rcu_read_lock(); + do_each_thread(temp, task) { + uid = from_kuid_munged(user_ns, task_uid(task)); + uid_entry = find_or_register_uid(uid); + if (!uid_entry) + continue; + add_uid_io_stats(uid_entry, task, UID_STATE_TOTAL_CURR); + } while_each_thread(temp, task); + rcu_read_unlock(); + + hash_for_each(hash_table, bkt, uid_entry, hash) { + compute_uid_io_bucket_stats(&uid_entry->io[uid_entry->state], + &uid_entry->io[UID_STATE_TOTAL_CURR], + &uid_entry->io[UID_STATE_TOTAL_LAST], + &uid_entry->io[UID_STATE_DEAD_TASKS]); + } +} + +static void update_io_stats_uid_locked(struct uid_entry *uid_entry) +{ + struct task_struct *task, *temp; + struct user_namespace *user_ns = current_user_ns(); + + memset(&uid_entry->io[UID_STATE_TOTAL_CURR], 0, + sizeof(struct io_stats)); + + rcu_read_lock(); + do_each_thread(temp, task) { + if (from_kuid_munged(user_ns, task_uid(task)) != uid_entry->uid) + continue; + add_uid_io_stats(uid_entry, task, UID_STATE_TOTAL_CURR); + } while_each_thread(temp, task); + rcu_read_unlock(); + + compute_uid_io_bucket_stats(&uid_entry->io[uid_entry->state], + &uid_entry->io[UID_STATE_TOTAL_CURR], + &uid_entry->io[UID_STATE_TOTAL_LAST], + &uid_entry->io[UID_STATE_DEAD_TASKS]); +} + +static int uid_io_show(struct seq_file *m, void *v) +{ + struct uid_entry *uid_entry; + unsigned long bkt; + + rt_mutex_lock(&uid_lock); + + update_io_stats_all_locked(); + + hash_for_each(hash_table, bkt, uid_entry, hash) { + seq_printf(m, "%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n", + uid_entry->uid, + uid_entry->io[UID_STATE_FOREGROUND].rchar, + uid_entry->io[UID_STATE_FOREGROUND].wchar, + uid_entry->io[UID_STATE_FOREGROUND].read_bytes, + uid_entry->io[UID_STATE_FOREGROUND].write_bytes, + uid_entry->io[UID_STATE_BACKGROUND].rchar, + uid_entry->io[UID_STATE_BACKGROUND].wchar, + uid_entry->io[UID_STATE_BACKGROUND].read_bytes, + uid_entry->io[UID_STATE_BACKGROUND].write_bytes, + uid_entry->io[UID_STATE_FOREGROUND].fsync, + uid_entry->io[UID_STATE_BACKGROUND].fsync); + } + + rt_mutex_unlock(&uid_lock); + + return 0; +} + +static int uid_io_open(struct inode *inode, struct file *file) +{ + return single_open(file, uid_io_show, PDE_DATA(inode)); +} + +static const struct file_operations uid_io_fops = { + .open = uid_io_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int uid_procstat_open(struct inode *inode, struct file *file) +{ + return single_open(file, NULL, NULL); +} + +static ssize_t uid_procstat_write(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + struct uid_entry *uid_entry; + uid_t uid; + int argc, state; + char input[128]; + + if (count >= sizeof(input)) + return -EINVAL; + + if (copy_from_user(input, buffer, count)) + return -EFAULT; + + input[count] = '\0'; + + argc = sscanf(input, "%u %d", &uid, &state); + if (argc != 2) + return -EINVAL; + + if (state != UID_STATE_BACKGROUND && state != UID_STATE_FOREGROUND) + return -EINVAL; + + rt_mutex_lock(&uid_lock); + + uid_entry = find_or_register_uid(uid); + if (!uid_entry) { + rt_mutex_unlock(&uid_lock); + return -EINVAL; + } + + if (uid_entry->state == state) { + rt_mutex_unlock(&uid_lock); + return count; + } + + update_io_stats_uid_locked(uid_entry); + + uid_entry->state = state; + + rt_mutex_unlock(&uid_lock); + + return count; +} + +static const struct file_operations uid_procstat_fops = { + .open = uid_procstat_open, + .release = single_release, + .write = uid_procstat_write, +}; + +static int process_notifier(struct notifier_block *self, + unsigned long cmd, void *v) +{ + struct task_struct *task = v; + struct uid_entry *uid_entry; + cputime_t utime, stime; + uid_t uid; + + if (!task) + return NOTIFY_OK; + + rt_mutex_lock(&uid_lock); + uid = from_kuid_munged(current_user_ns(), task_uid(task)); + uid_entry = find_or_register_uid(uid); + if (!uid_entry) { + pr_err("%s: failed to find uid %d\n", __func__, uid); + goto exit; + } + + task_cputime_adjusted(task, &utime, &stime); + uid_entry->utime += utime; + uid_entry->stime += stime; + uid_entry->power += task->cpu_power; + task->cpu_power = ULLONG_MAX; + + add_uid_io_stats(uid_entry, task, UID_STATE_DEAD_TASKS); + +exit: + rt_mutex_unlock(&uid_lock); + return NOTIFY_OK; +} + +static struct notifier_block process_notifier_block = { + .notifier_call = process_notifier, +}; + +static int __init proc_uid_sys_stats_init(void) +{ + hash_init(hash_table); + + cpu_parent = proc_mkdir("uid_cputime", NULL); + if (!cpu_parent) { + pr_err("%s: failed to create uid_cputime proc entry\n", + __func__); + goto err; + } + + proc_create_data("remove_uid_range", 0222, cpu_parent, + &uid_remove_fops, NULL); + proc_create_data("show_uid_stat", 0444, cpu_parent, + &uid_cputime_fops, NULL); + + io_parent = proc_mkdir("uid_io", NULL); + if (!io_parent) { + pr_err("%s: failed to create uid_io proc entry\n", + __func__); + goto err; + } + + proc_create_data("stats", 0444, io_parent, + &uid_io_fops, NULL); + + proc_parent = proc_mkdir("uid_procstat", NULL); + if (!proc_parent) { + pr_err("%s: failed to create uid_procstat proc entry\n", + __func__); + goto err; + } + + proc_create_data("set", 0222, proc_parent, + &uid_procstat_fops, NULL); + + profile_event_register(PROFILE_TASK_EXIT, &process_notifier_block); + + return 0; + +err: + remove_proc_subtree("uid_cputime", NULL); + remove_proc_subtree("uid_io", NULL); + remove_proc_subtree("uid_procstat", NULL); + return -ENOMEM; +} + +early_initcall(proc_uid_sys_stats_init); diff --git a/drivers/net/usb/cdc-phonet.c b/drivers/net/usb/cdc-phonet.c index 7d78669000d70..af49e8fcd7113 100644 --- a/drivers/net/usb/cdc-phonet.c +++ b/drivers/net/usb/cdc-phonet.c @@ -343,9 +343,9 @@ int usbpn_probe(struct usb_interface *intf, const struct usb_device_id *id) data = intf->altsetting->extra; len = intf->altsetting->extralen; - while (len >= 3) { + while (len > 0) { u8 dlen = data[0]; - if (dlen < 3) + if ((len < dlen) || (dlen < 3 )) return -EINVAL; /* bDescriptorType */ diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index ea0a686a09ebd..1bcb77a9f4df7 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -128,7 +128,13 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf) memset(info, 0, sizeof *info); info->control = intf; - while (len > 3) { + while (len > 0) { + + if ((len < buf [0]) || (buf [0] < 3)) { + dev_dbg(&intf->dev, "invalid descriptor buffer length\n"); + goto bad_desc; + } + if (buf [1] != USB_DT_CS_INTERFACE) goto next_desc; diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 12aaf1f4f8902..95b616bf48f25 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -235,9 +235,14 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) info->data = intf; /* and a number of CDC descriptors */ - while (len > 3) { + while (len > 0) { struct usb_descriptor_header *h = (void *)buf; + if ((len < buf[0]) || (buf[0] < 3)) { + dev_dbg(&intf->dev, "invalid descriptor buffer length\n"); + goto err; + } + /* ignore any misplaced descriptors */ if (h->bDescriptorType != USB_DT_CS_INTERFACE) goto next_desc; diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c index 5a714ba555327..643682f4c876c 100644 --- a/drivers/net/wireless/wcnss/wcnss_wlan.c +++ b/drivers/net/wireless/wcnss/wcnss_wlan.c @@ -198,7 +198,7 @@ static DEFINE_SPINLOCK(reg_spinlock); #define WCNSS_USR_HAS_CAL_DATA (WCNSS_USR_CTRL_MSG_START + 2) #define WCNSS_USR_WLAN_MAC_ADDR (WCNSS_USR_CTRL_MSG_START + 3) -#define MAC_ADDRESS_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define MAC_ADDRESS_STR "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" /* message types */ #define WCNSS_CTRL_MSG_START 0x01000000 diff --git a/drivers/platform/msm/ipa/ipa_hdr.c b/drivers/platform/msm/ipa/ipa_hdr.c index 702771a0080ba..3f33d9138ee66 100644 --- a/drivers/platform/msm/ipa/ipa_hdr.c +++ b/drivers/platform/msm/ipa/ipa_hdr.c @@ -809,8 +809,17 @@ int __ipa_del_hdr(u32 hdr_hdl, bool by_user) return -EINVAL; } - if (by_user) + if (by_user) { + if (!strcmp(entry->name, IPA_LAN_RX_HDR_NAME)) { + IPADBG("Trying to delete hdr %s offset=%u\n", + entry->name, entry->offset_entry->offset); + if (!entry->offset_entry->offset) { + IPAERR("User cannot delete default header\n"); + return -EPERM; + } + } entry->user_deleted = true; + } if (--entry->ref_cnt) { IPADBG("hdr_hdl %x ref_cnt %d\n", hdr_hdl, entry->ref_cnt); @@ -1113,8 +1122,19 @@ int ipa_reset_hdr(void) &ipa_ctx->hdr_tbl.head_hdr_entry_list, link) { /* do not remove the default header */ - if (!strcmp(entry->name, IPA_LAN_RX_HDR_NAME)) - continue; + if (!strcmp(entry->name, IPA_LAN_RX_HDR_NAME)) { + IPADBG("Trying to remove hdr %s offset=%u\n", + entry->name, entry->offset_entry->offset); + if (!entry->offset_entry->offset) { + if (entry->is_hdr_proc_ctx) { + mutex_unlock(&ipa_ctx->lock); + WARN_ON(1); + return -EFAULT; + } + IPADBG("skip default header\n"); + continue; + } + } if (ipa_id_find(entry->id) == NULL) { WARN_ON(1); diff --git a/drivers/platform/msm/ipa/ipa_rt.c b/drivers/platform/msm/ipa/ipa_rt.c index 9d2f1df371030..db3c469cb83f2 100644 --- a/drivers/platform/msm/ipa/ipa_rt.c +++ b/drivers/platform/msm/ipa/ipa_rt.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1072,6 +1072,15 @@ int __ipa_del_rt_rule(u32 rule_hdl) return -EINVAL; } + if (!strcmp(entry->tbl->name, IPA_DFLT_RT_TBL_NAME)) { + IPADBG("Deleting rule from default rt table idx=%u\n", + entry->tbl->idx); + if (entry->tbl->rule_cnt == 1) { + IPAERR("Default tbl last rule cannot be deleted\n"); + return -EINVAL; + } + } + if (entry->hdr) __ipa_release_hdr(entry->hdr->id); else if (entry->proc_ctx) diff --git a/drivers/platform/msm/ipa/rmnet_ipa.c b/drivers/platform/msm/ipa/rmnet_ipa.c index 3eb2674f4ba36..658ad89aa3607 100644 --- a/drivers/platform/msm/ipa/rmnet_ipa.c +++ b/drivers/platform/msm/ipa/rmnet_ipa.c @@ -1360,6 +1360,7 @@ static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) mutex_lock(&add_mux_channel_lock); /* already got Q6 UL filter rules*/ rc = wwan_add_ul_flt_rule_to_ipa(); + mutex_unlock(&add_mux_channel_lock); egress_set = true; if (rc) IPAWANERR("install UL rules failed\n"); diff --git a/drivers/power/qcom/Kconfig b/drivers/power/qcom/Kconfig index 70edaeea2026d..afc41e07b72d3 100644 --- a/drivers/power/qcom/Kconfig +++ b/drivers/power/qcom/Kconfig @@ -1,7 +1,6 @@ config MSM_PM depends on PM select MSM_IDLE_STATS if DEBUG_FS - select CPU_IDLE_MULTIPLE_DRIVERS bool "Qualcomm platform specific PM driver" help Platform specific power driver to manage cores and l2 diff --git a/drivers/power/qcom/msm-core.c b/drivers/power/qcom/msm-core.c index 1b9872a4150df..1b98a090c36cd 100644 --- a/drivers/power/qcom/msm-core.c +++ b/drivers/power/qcom/msm-core.c @@ -528,7 +528,7 @@ static long msm_core_ioctl(struct file *file, unsigned int cmd, node->sp->voltage, sizeof(uint32_t) * node->sp->num_of_freqs); if (ret) - break; + goto unlock; for (i = 0; i < node->sp->num_of_freqs; i++) { ret = copy_to_user((void __user *)&argp->freq[i], &node->sp->table[i].frequency, diff --git a/drivers/pwm/pwm-qpnp.c b/drivers/pwm/pwm-qpnp.c index 0ab6af3e59b53..cee4bfa0825db 100644 --- a/drivers/pwm/pwm-qpnp.c +++ b/drivers/pwm/pwm-qpnp.c @@ -1110,6 +1110,88 @@ static int qpnp_lpg_configure_lut_state(struct qpnp_pwm_chip *chip, return rc; } +static int qpnp_lpg_configure_lut_states(struct qpnp_pwm_chip **chips, + int num, enum qpnp_lut_state state) +{ + struct qpnp_pwm_chip *chip; + struct qpnp_lpg_config *lpg_config; + u8 value1, value2, mask1, mask2; + u8 *reg1, *reg2; + u16 addr, addr1; + int rc, i; + bool test_enable; + u8 ramp_en = 0, ramp_mask = 0; + + + for (i = 0; i < num; i++) { + chip = chips[i]; + lpg_config = &chip->lpg_config; + value1 = chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL]; + reg1 = &chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL]; + reg2 = &chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]; + mask2 = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK | + QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK; + if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE) + mask2 |= QPNP_EN_PWM_OUTPUT_MASK; + + if (chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE + && chip->revision == QPNP_LPG_REVISION_0) { + if (state == QPNP_LUT_ENABLE) { + QPNP_ENABLE_LUT_V0(value1); + value2 = QPNP_ENABLE_LPG_MODE(chip); + } else { + QPNP_DISABLE_LUT_V0(value1); + value2 = QPNP_DISABLE_LPG_MODE(chip); + } + mask1 = QPNP_RAMP_START_MASK; + addr1 = SPMI_LPG_REG_ADDR(lpg_config->base_addr, + QPNP_RAMP_CONTROL); + } else if ((chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE + && chip->revision == QPNP_LPG_REVISION_1) + || chip->sub_type == QPNP_LPG_S_CHAN_SUB_TYPE) { + if (state == QPNP_LUT_ENABLE) { + QPNP_ENABLE_LUT_V1(value1, + lpg_config->lut_config.ramp_index); + value2 = QPNP_ENABLE_LPG_MODE(chip); + } else { + value2 = QPNP_DISABLE_LPG_MODE(chip); + } + mask1 = value1; + addr1 = lpg_config->lut_base_addr + + SPMI_LPG_REV1_RAMP_CONTROL_OFFSET; + } else { + pr_err("Unsupported LPG subtype 0x%02x, revision 0x%02x\n", + chip->sub_type, chip->revision); + return -EINVAL; + } + + addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr, + QPNP_ENABLE_CONTROL); + + if (chip->in_test_mode) { + test_enable = (state == QPNP_LUT_ENABLE) ? 1 : 0; + rc = qpnp_dtest_config(chip, test_enable); + if (rc) + pr_err("Failed to configure TEST mode\n"); + } + + rc = qpnp_lpg_save_and_write(value2, mask2, reg2, + addr, 1, chip); + if (rc) + return rc; + + ramp_en |= value1; + ramp_mask |= mask1; + } + + if (state == QPNP_LUT_ENABLE + || (chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE + && chip->revision == QPNP_LPG_REVISION_0)) + rc = qpnp_lpg_save_and_write(ramp_en, ramp_mask, reg1, + addr1, 1, chip); + return rc; +} + static inline int qpnp_enable_pwm_mode(struct qpnp_pwm_chip *chip) { if (chip->pwm_config.supported_sizes == QPNP_PWM_SIZE_7_8_BIT) @@ -1371,6 +1453,49 @@ static int qpnp_pwm_enable(struct pwm_chip *pwm_chip, return rc; } +int pwm_enable_synchronized(struct pwm_device **pwms, size_t num) +{ + unsigned long *flags; + struct qpnp_pwm_chip **chips; + int rc = 0, i; + + if (pwms == NULL || IS_ERR(pwms) || num == 0) { + pr_err("Invalid pwm handle or idx_len=0\n"); + return -EINVAL; + } + + flags = kzalloc(sizeof(unsigned long) * num, GFP_KERNEL); + if (!flags) + return -ENOMEM; + + chips = kzalloc(sizeof(struct qpnp_pwm_chip *) * num, GFP_KERNEL); + if (!chips) { + kfree(flags); + return -ENOMEM; + } + + for (i = 0; i < num; i++) { + chips[i] = qpnp_pwm_from_pwm_dev(pwms[i]); + if (chips[i] == NULL) { + rc = -EINVAL; + goto err_failed; + } + spin_lock_irqsave(&chips[i]->lpg_lock, flags[i]); + } + + rc = qpnp_lpg_configure_lut_states(chips, num, QPNP_LUT_ENABLE); + +err_failed: + while(i > 0) { + spin_unlock_irqrestore(&chips[i - 1]->lpg_lock, flags[i - 1]); + i--; + } + kfree(flags); + kfree(chips); + return rc; +} +EXPORT_SYMBOL_GPL(pwm_enable_synchronized); + /** * qpnp_pwm_disable - stop a PWM output toggling * @pwm_chip: the PWM chip diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 446b85110a1fc..09625081b56c0 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -684,6 +684,7 @@ int sas_smp_get_phy_events(struct sas_phy *phy) phy->phy_reset_problem_count = scsi_to_u32(&resp[24]); out: + kfree(req); kfree(resp); return res; diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index 3810c3ff36a2d..4bd31b6adedef 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -400,6 +400,9 @@ sg_read(struct file *filp, char __user *buf, size_t count, loff_t * ppos) struct sg_header *old_hdr = NULL; int retval = 0; + if (unlikely(segment_eq(get_fs(), KERNEL_DS))) + return -EINVAL; + if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp))) return -ENXIO; SCSI_LOG_TIMEOUT(3, printk("sg_read: %s, count=%d\n", @@ -2033,11 +2036,12 @@ sg_get_rq_mark(Sg_fd * sfp, int pack_id) if ((1 == resp->done) && (!resp->sg_io_owned) && ((-1 == pack_id) || (resp->header.pack_id == pack_id))) { resp->done = 2; /* guard against other readers */ - break; + write_unlock_irqrestore(&sfp->rq_list_lock, iflags); + return resp; } } write_unlock_irqrestore(&sfp->rq_list_lock, iflags); - return resp; + return NULL; } /* always adds to end of list */ diff --git a/drivers/soc/qcom/mpm-of.c b/drivers/soc/qcom/mpm-of.c index fa441639dd6cf..ea69ba60072cc 100644 --- a/drivers/soc/qcom/mpm-of.c +++ b/drivers/soc/qcom/mpm-of.c @@ -528,7 +528,7 @@ bool msm_mpm_irqs_detectable(bool from_idle) from_idle); } -void msm_mpm_enter_sleep(uint64_t sclk_count, bool from_idle, +void msm_mpm_enter_sleep(uint32_t sclk_count, bool from_idle, const struct cpumask *cpumask) { cycle_t wakeup = (u64)sclk_count * ARCH_TIMER_HZ; diff --git a/drivers/soc/qcom/qdsp6v2/apr.c b/drivers/soc/qcom/qdsp6v2/apr.c index ae6acb81018e4..7c945dff8daf0 100644 --- a/drivers/soc/qcom/qdsp6v2/apr.c +++ b/drivers/soc/qcom/qdsp6v2/apr.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2015, 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2015, 2016, 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -404,19 +404,19 @@ struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn, mutex_unlock(&svc->m_lock); return NULL; } - if (!svc->port_cnt && !svc->svc_cnt) + if (!svc->svc_cnt) clnt->svc_cnt++; svc->port_cnt++; svc->port_fn[temp_port] = svc_fn; svc->port_priv[temp_port] = priv; + svc->svc_cnt++; } else { if (!svc->fn) { - if (!svc->port_cnt && !svc->svc_cnt) + if (!svc->svc_cnt) clnt->svc_cnt++; svc->fn = svc_fn; - if (svc->port_cnt) - svc->svc_cnt++; svc->priv = priv; + svc->svc_cnt++; } } @@ -551,7 +551,8 @@ void apr_cb_func(void *buf, int len, void *priv) temp_port = ((data.dest_port >> 8) * 8) + (data.dest_port & 0xFF); pr_debug("port = %d t_port = %d\n", data.src_port, temp_port); - if (c_svc->port_cnt && c_svc->port_fn[temp_port]) + if (((temp_port >= 0) && (temp_port < APR_MAX_PORTS)) + && (c_svc->port_cnt && c_svc->port_fn[temp_port])) c_svc->port_fn[temp_port](&data, c_svc->port_priv[temp_port]); else if (c_svc->fn) c_svc->fn(&data, c_svc->priv); @@ -621,24 +622,17 @@ int apr_deregister(void *handle) client_id = svc->client_id; clnt = &client[dest_id][client_id]; - if (svc->port_cnt > 0 || svc->svc_cnt > 0) { + if (svc->svc_cnt > 0) { if (svc->port_cnt) svc->port_cnt--; - else if (svc->svc_cnt) - svc->svc_cnt--; - if (!svc->port_cnt && !svc->svc_cnt) { + svc->svc_cnt--; + if (!svc->svc_cnt) { client[dest_id][client_id].svc_cnt--; - svc->need_reset = 0x0; - } - } else if (client[dest_id][client_id].svc_cnt > 0) { - client[dest_id][client_id].svc_cnt--; - if (!client[dest_id][client_id].svc_cnt) { - svc->need_reset = 0x0; pr_debug("%s: service is reset %pK\n", __func__, svc); } } - if (!svc->port_cnt && !svc->svc_cnt) { + if (!svc->svc_cnt) { svc->priv = NULL; svc->id = 0; svc->fn = NULL; diff --git a/drivers/soc/qcom/qdsp6v2/voice_svc.c b/drivers/soc/qcom/qdsp6v2/voice_svc.c index fe54589744061..e05230255d6f0 100644 --- a/drivers/soc/qcom/qdsp6v2/voice_svc.c +++ b/drivers/soc/qcom/qdsp6v2/voice_svc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -67,7 +68,11 @@ static void *dummy_q6_mvm; static void *dummy_q6_cvs; dev_t device_num; +spinlock_t voicesvc_lock; +static bool is_released; static int voice_svc_dummy_reg(void); +static int voice_svc_dummy_dereg(void); + static int32_t qdsp_dummy_apr_callback(struct apr_client_data *data, void *priv); @@ -82,10 +87,16 @@ static int32_t qdsp_apr_callback(struct apr_client_data *data, void *priv) return -EINVAL; } + spin_lock(&voicesvc_lock); + if (is_released) { + spin_unlock(&voicesvc_lock); + return 0; + } prtd = (struct voice_svc_prvt *)priv; if (prtd == NULL) { pr_err("%s: private data is NULL\n", __func__); + spin_unlock(&voicesvc_lock); return -EINVAL; } @@ -128,6 +139,7 @@ static int32_t qdsp_apr_callback(struct apr_client_data *data, void *priv) spin_unlock_irqrestore(&prtd->response_lock, spin_flags); + spin_unlock(&voicesvc_lock); return -ENOMEM; } @@ -156,6 +168,7 @@ static int32_t qdsp_apr_callback(struct apr_client_data *data, void *priv) __func__); } + spin_unlock(&voicesvc_lock); return 0; } @@ -614,6 +627,21 @@ static int voice_svc_dummy_reg() return -EINVAL; } +static int voice_svc_dummy_dereg(void) +{ + pr_debug("%s\n", __func__); + if (dummy_q6_mvm != NULL) { + apr_deregister(dummy_q6_mvm); + dummy_q6_mvm = NULL; + } + + if (dummy_q6_cvs != NULL) { + apr_deregister(dummy_q6_cvs); + dummy_q6_cvs = NULL; + } + return 0; +} + static int voice_svc_open(struct inode *inode, struct file *file) { struct voice_svc_prvt *prtd = NULL; @@ -637,6 +665,7 @@ static int voice_svc_open(struct inode *inode, struct file *file) mutex_init(&prtd->response_mutex_lock); file->private_data = (void *)prtd; + is_released = 0; /* Current APR implementation doesn't support session based * multiple service registrations. The apr_deregister() * function sets the destination and client IDs to zero, if @@ -669,6 +698,11 @@ static int voice_svc_release(struct inode *inode, struct file *file) goto done; } + mutex_lock(&prtd->response_mutex_lock); + if (reg_dummy_sess) { + voice_svc_dummy_dereg(); + reg_dummy_sess = 0; + } if (prtd->apr_q6_cvs != NULL) { svc_name = VOICE_SVC_MVM_STR; handle = &prtd->apr_q6_cvs; @@ -685,7 +719,6 @@ static int voice_svc_release(struct inode *inode, struct file *file) pr_err("%s: Failed to dereg MVM %d\n", __func__, ret); } - mutex_lock(&prtd->response_mutex_lock); spin_lock_irqsave(&prtd->response_lock, spin_flags); while (!list_empty(&prtd->response_queue)) { @@ -703,9 +736,11 @@ static int voice_svc_release(struct inode *inode, struct file *file) mutex_destroy(&prtd->response_mutex_lock); + spin_lock(&voicesvc_lock); kfree(file->private_data); file->private_data = NULL; - + is_released = 1; + spin_unlock(&voicesvc_lock); done: return ret; } @@ -774,6 +809,7 @@ static int voice_svc_probe(struct platform_device *pdev) goto add_err; } pr_debug("%s: Device created\n", __func__); + spin_lock_init(&voicesvc_lock); goto done; add_err: diff --git a/drivers/soc/qcom/rpm-smd-debug.c b/drivers/soc/qcom/rpm-smd-debug.c index 1c0a05c87edaf..8194fe0cda5b6 100644 --- a/drivers/soc/qcom/rpm-smd-debug.c +++ b/drivers/soc/qcom/rpm-smd-debug.c @@ -99,23 +99,23 @@ static ssize_t rsc_ops_write(struct file *fp, const char __user *user_buffer, cmp += pos; if (sscanf(cmp, "%5s %n", key_str, &pos) != 1) { pr_err("Invalid number of arguments passed\n"); - goto err; + goto err_request; } if (strlen(key_str) > 4) { pr_err("Key value cannot be more than 4 charecters"); - goto err; + goto err_request; } key = string_to_uint(key_str); if (!key) { pr_err("Key values entered incorrectly\n"); - goto err; + goto err_request; } cmp += pos; if (sscanf(cmp, "%u %n", &data, &pos) != 1) { pr_err("Invalid number of arguments passed\n"); - goto err; + goto err_request; } if (msm_rpm_add_kvp_data(req, key, diff --git a/drivers/soc/qcom/spm_devices.c b/drivers/soc/qcom/spm_devices.c index b773d0b1b32ce..849eb3901d8f4 100644 --- a/drivers/soc/qcom/spm_devices.c +++ b/drivers/soc/qcom/spm_devices.c @@ -186,7 +186,7 @@ static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode) } static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, - unsigned int mode, bool notify_rpm, bool set_spm_enable) + unsigned int mode, bool notify_rpm) { uint32_t i; uint32_t start_addr = 0; @@ -200,11 +200,9 @@ static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, || (mode == MSM_SPM_MODE_GDHS)) pc_mode = true; - if (mode == MSM_SPM_MODE_DISABLED && set_spm_enable) { + if (mode == MSM_SPM_MODE_DISABLED) { ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false); - } else { - if (set_spm_enable) - ret = msm_spm_drv_set_spm_enable(&dev->reg_data, true); + } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) { for (i = 0; i < dev->num_modes; i++) { if (dev->modes[i].mode != mode) continue; @@ -354,7 +352,7 @@ bool msm_spm_is_mode_avail(unsigned int mode) int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) { struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); - return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm, true); + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); } EXPORT_SYMBOL(msm_spm_set_low_power_mode); @@ -396,16 +394,10 @@ struct msm_spm_device *msm_spm_get_device_by_name(const char *name) return ERR_PTR(-ENODEV); } -int msm_spm_config_low_power_mode_addr(struct msm_spm_device *dev, - unsigned int mode, bool notify_rpm) -{ - return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm, false); -} - int msm_spm_config_low_power_mode(struct msm_spm_device *dev, unsigned int mode, bool notify_rpm) { - return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm, true); + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); } #ifdef CONFIG_MSM_L2_SPM diff --git a/drivers/soc/qcom/subsys-pil-tz.c b/drivers/soc/qcom/subsys-pil-tz.c index 001cc25bd4e3a..cd44f5ae6db99 100644 --- a/drivers/soc/qcom/subsys-pil-tz.c +++ b/drivers/soc/qcom/subsys-pil-tz.c @@ -969,6 +969,7 @@ static int pil_tz_driver_probe(struct platform_device *pdev) destroy_ramdump_device(d->ramdump_dev); err_ramdump: pil_desc_release(&d->desc); + platform_set_drvdata(pdev, NULL); return rc; } diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt.h b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt.h index c608cf59413af..129443fe84b29 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt.h +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt.h @@ -6315,6 +6315,9 @@ PREPACK struct htt_txq_group { #define HTT_TX_COMPL_IND_APPEND_GET(_info) \ (((_info) & HTT_TX_COMPL_IND_APPEND_M) >> HTT_TX_COMPL_IND_APPEND_S) +#define HTT_TX_COMPL_HEAD_SZ 4 +#define HTT_TX_COMPL_BYTES_PER_MSDU_ID 2 + #define HTT_TX_COMPL_CTXT_SZ sizeof(A_UINT16) #define HTT_TX_COMPL_CTXT_NUM(_bytes) ((_bytes) >> 1) diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt_t2h.c b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt_t2h.c index b736a970c5750..9b2a2f12a6b1d 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt_t2h.c +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/HTT/htt_t2h.c @@ -172,7 +172,7 @@ htt_t2h_lp_msg_handler(void *context, adf_nbuf_t htt_t2h_msg ) { u_int16_t peer_id; u_int8_t tid; - int seq_num_start, seq_num_end; + u_int16_t seq_num_start, seq_num_end; enum htt_rx_flush_action action; peer_id = HTT_RX_FLUSH_PEER_ID_GET(*msg_word); @@ -297,6 +297,14 @@ htt_t2h_lp_msg_handler(void *context, adf_nbuf_t htt_t2h_msg ) peer_mac_addr = htt_t2h_mac_addr_deswizzle( (u_int8_t *) (msg_word+1), &mac_addr_deswizzle_buf[0]); + if (peer_id > ol_cfg_max_peer_id(pdev->ctrl_pdev)) { + adf_os_print("%s: HTT_T2H_MSG_TYPE_PEER_MAP," + "invalid peer_id, %u\n", + __FUNCTION__, + peer_id); + break; + } + ol_rx_peer_map_handler( pdev->txrx_pdev, peer_id, vdev_id, peer_mac_addr, 1/*can tx*/); break; @@ -306,6 +314,14 @@ htt_t2h_lp_msg_handler(void *context, adf_nbuf_t htt_t2h_msg ) u_int16_t peer_id; peer_id = HTT_RX_PEER_UNMAP_PEER_ID_GET(*msg_word); + if (peer_id > ol_cfg_max_peer_id(pdev->ctrl_pdev)) { + adf_os_print("%s: HTT_T2H_MSG_TYPE_PEER_UNMAP," + "invalid peer_id, %u\n", + __FUNCTION__, + peer_id); + break; + } + ol_rx_peer_unmap_handler(pdev->txrx_pdev, peer_id); break; } @@ -630,10 +646,26 @@ if (adf_os_unlikely(pdev->rx_ring.rx_reset)) { { int num_msdus; enum htt_tx_status status; + int msg_len = adf_nbuf_len(htt_t2h_msg); /* status - no enum translation needed */ status = HTT_TX_COMPL_IND_STATUS_GET(*msg_word); num_msdus = HTT_TX_COMPL_IND_NUM_GET(*msg_word); + + /* + * each desc id will occupy 2 bytes. + * the 4 is for htt msg header + */ + if ((num_msdus * HTT_TX_COMPL_BYTES_PER_MSDU_ID + + HTT_TX_COMPL_HEAD_SZ) > msg_len) { + adf_os_print("%s: num_msdus(%d) is invalid," + "adf_nbuf_len = %d\n", + __FUNCTION__, + num_msdus, + msg_len); + break; + } + if (num_msdus & 0x1) { struct htt_tx_compl_ind_base *compl = (void *)msg_word; @@ -675,7 +707,7 @@ if (adf_os_unlikely(pdev->rx_ring.rx_reset)) { { u_int16_t peer_id; u_int8_t tid, pn_ie_cnt, *pn_ie=NULL; - int seq_num_start, seq_num_end; + u_int16_t seq_num_start, seq_num_end; /*First dword */ peer_id = HTT_RX_PN_IND_PEER_ID_GET(*msg_word); @@ -702,8 +734,23 @@ if (adf_os_unlikely(pdev->rx_ring.rx_reset)) { case HTT_T2H_MSG_TYPE_TX_INSPECT_IND: { int num_msdus; + int msg_len = adf_nbuf_len(htt_t2h_msg); num_msdus = HTT_TX_COMPL_IND_NUM_GET(*msg_word); + /* + * each desc id will occupy 2 bytes. + * the 4 is for htt msg header + */ + if ((num_msdus * HTT_TX_COMPL_BYTES_PER_MSDU_ID + + HTT_TX_COMPL_HEAD_SZ) > msg_len) { + adf_os_print("%s: num_msdus(%d) is invalid," + "adf_nbuf_len = %d,inspect\n", + __FUNCTION__, + num_msdus, + msg_len); + break; + } + if (num_msdus & 0x1) { struct htt_tx_compl_ind_base *compl = (void *)msg_word; @@ -1077,8 +1124,8 @@ void htt_rx_frag_ind_flush_seq_num_range( htt_pdev_handle pdev, adf_nbuf_t rx_frag_ind_msg, - int *seq_num_start, - int *seq_num_end) + u_int16_t *seq_num_start, + u_int16_t *seq_num_end) { u_int32_t *msg_word; diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TLSHIM/tl_shim.c b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TLSHIM/tl_shim.c index 3ebe957c5d794..b09445f59fd45 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TLSHIM/tl_shim.c +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TLSHIM/tl_shim.c @@ -642,6 +642,16 @@ static int tlshim_mgmt_rx_process(void *context, u_int8_t *data, rx_pkt->pkt_meta.mpdu_data_len = hdr->buf_len - rx_pkt->pkt_meta.mpdu_hdr_len; + /* + * If the mpdu_data_len is greater than Max (2k), drop the frame + */ + if (rx_pkt->pkt_meta.mpdu_data_len > WMA_MAX_MGMT_MPDU_LEN) { + TLSHIM_LOGE("Data Len %d greater than max, dropping frame", + rx_pkt->pkt_meta.mpdu_data_len); + vos_mem_free(rx_pkt); + return 0; + } + /* * saved_beacon means this beacon is a duplicate of one * sent earlier. roamCandidateInd flag is used to indicate to diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.c b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.c index 358d6d660c3bf..420683bf9cf5c 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.c +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2014, 2016 The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2014, 2016-2017 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -192,12 +192,20 @@ ol_rx_frag_indication_handler( u_int8_t tid) { u_int16_t seq_num; - int seq_num_start, seq_num_end; + u_int16_t seq_num_start, seq_num_end; struct ol_txrx_peer_t *peer; htt_pdev_handle htt_pdev; adf_nbuf_t head_msdu, tail_msdu; void *rx_mpdu_desc; + if (tid >= OL_TXRX_NUM_EXT_TIDS) { + TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, + "%s: invalid tid, %u\n", + __FUNCTION__, + tid); + return; + } + htt_pdev = pdev->htt_pdev; peer = ol_txrx_peer_find_by_id(pdev, peer_id); @@ -245,7 +253,7 @@ ol_rx_reorder_flush_frag( htt_pdev_handle htt_pdev, struct ol_txrx_peer_t *peer, unsigned tid, - int seq_num) + u_int16_t seq_num) { struct ol_rx_reorder_array_elem_t *rx_reorder_array_elem; int seq; diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.h b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.h index 737c299873057..7750f2456fd68 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.h +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_defrag.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2014 The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2014, 2017 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -165,7 +165,7 @@ ol_rx_reorder_flush_frag( htt_pdev_handle htt_pdev, struct ol_txrx_peer_t *peer, unsigned tid, - int seq_num); + u_int16_t seq_num); static inline void xor_block( diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_reorder.c b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_reorder.c index e1ffff092a92f..05f54907bb509 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_reorder.c +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_rx_reorder.c @@ -583,6 +583,14 @@ ol_rx_flush_handler( struct ol_rx_reorder_array_elem_t *rx_reorder_array_elem; htt_pdev_handle htt_pdev = pdev->htt_pdev; + if (tid >= OL_TXRX_NUM_EXT_TIDS) { + TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, + "%s: invalid tid, %u\n", + __FUNCTION__, + tid); + return; + } + peer = ol_txrx_peer_find_by_id(pdev, peer_id); if (peer) { vdev = peer->vdev; @@ -622,8 +630,8 @@ ol_rx_pn_ind_handler( ol_txrx_pdev_handle pdev, u_int16_t peer_id, u_int8_t tid, - int seq_num_start, - int seq_num_end, + u_int16_t seq_num_start, + u_int16_t seq_num_end, u_int8_t pn_ie_cnt, u_int8_t *pn_ie) { @@ -635,7 +643,8 @@ ol_rx_pn_ind_handler( adf_nbuf_t head_msdu = NULL; adf_nbuf_t tail_msdu = NULL; htt_pdev_handle htt_pdev = pdev->htt_pdev; - int seq_num, i=0; + u_int16_t seq_num; + int i=0; peer = ol_txrx_peer_find_by_id(pdev, peer_id); diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx.c b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx.c index 7cd0917fc95d8..98961ca06d3a3 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx.c +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx.c @@ -376,6 +376,9 @@ ol_txrx_pdev_attach( TXRX_STATS_INIT(pdev); TAILQ_INIT(&pdev->vdev_list); + TAILQ_INIT(&pdev->req_list); + pdev->req_list_depth = 0; + adf_os_spinlock_init(&pdev->req_list_spinlock); /* do initial set up of the peer ID -> peer object lookup map */ if (ol_txrx_peer_find_attach(pdev)) { @@ -886,7 +889,9 @@ A_STATUS ol_txrx_pdev_attach_target(ol_txrx_pdev_handle pdev) void ol_txrx_pdev_detach(ol_txrx_pdev_handle pdev, int force) { - int i; + int i = 0; + unsigned int page_idx; + struct ol_txrx_stats_req_internal *req; /*checking to ensure txrx pdev structure is not NULL */ if (!pdev) { @@ -899,6 +904,30 @@ ol_txrx_pdev_detach(ol_txrx_pdev_handle pdev, int force) /* check that the pdev has no vdevs allocated */ TXRX_ASSERT1(TAILQ_EMPTY(&pdev->vdev_list)); + adf_os_spin_lock_bh(&pdev->req_list_spinlock); + if (pdev->req_list_depth > 0) + TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, + "Warning: the txrx req list is not empty, depth=%d\n", + pdev->req_list_depth + ); + TAILQ_FOREACH(req, &pdev->req_list, req_list_elem) { + TAILQ_REMOVE(&pdev->req_list, req, req_list_elem); + pdev->req_list_depth--; + TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, + "%d: %p,verbose(%d), concise(%d), up_m(0x%x), reset_m(0x%x)\n", + i++, + req, + req->base.print.verbose, + req->base.print.concise, + req->base.stats_type_upload_mask, + req->base.stats_type_reset_mask + ); + adf_os_mem_free(req); + } + adf_os_spin_unlock_bh(&pdev->req_list_spinlock); + + adf_os_spinlock_destroy(&pdev->req_list_spinlock); + OL_RX_REORDER_TIMEOUT_CLEANUP(pdev); if (ol_cfg_is_high_latency(pdev->ctrl_pdev)) { @@ -1961,12 +1990,6 @@ void ol_txrx_print_level_set(unsigned level) #endif } -struct ol_txrx_stats_req_internal { - struct ol_txrx_stats_req base; - int serviced; /* state of this request */ - int offset; -}; - static inline u_int64_t OL_TXRX_STATS_PTR_TO_U64(struct ol_txrx_stats_req_internal *req) { @@ -2028,6 +2051,11 @@ ol_txrx_fw_stats_get( /* use the non-volatile request object's address as the cookie */ cookie = OL_TXRX_STATS_PTR_TO_U64(non_volatile_req); + adf_os_spin_lock_bh(&pdev->req_list_spinlock); + TAILQ_INSERT_TAIL(&pdev->req_list, non_volatile_req, req_list_elem); + pdev->req_list_depth++; + adf_os_spin_unlock_bh(&pdev->req_list_spinlock); + if (htt_h2t_dbg_stats_get( pdev->htt_pdev, req->stats_type_upload_mask, @@ -2035,14 +2063,15 @@ ol_txrx_fw_stats_get( HTT_H2T_STATS_REQ_CFG_STAT_TYPE_INVALID, 0, cookie)) { + adf_os_spin_lock_bh(&pdev->req_list_spinlock); + TAILQ_REMOVE(&pdev->req_list, non_volatile_req, req_list_elem); + pdev->req_list_depth--; + adf_os_spin_unlock_bh(&pdev->req_list_spinlock); + adf_os_mem_free(non_volatile_req); return A_ERROR; } - if (req->wait.blocking) { - while (adf_os_mutex_acquire(pdev->osdev, req->wait.sem_ptr)) {} - } - return A_OK; } #endif @@ -2056,11 +2085,27 @@ ol_txrx_fw_stats_handler( enum htt_dbg_stats_status status; int length; u_int8_t *stats_data; - struct ol_txrx_stats_req_internal *req; + struct ol_txrx_stats_req_internal *req, *tmp; int more = 0; + int found = 0; req = OL_TXRX_U64_TO_STATS_PTR(cookie); + adf_os_spin_lock_bh(&pdev->req_list_spinlock); + TAILQ_FOREACH(tmp, &pdev->req_list, req_list_elem) { + if (req == tmp) { + found = 1; + break; + } + } + adf_os_spin_unlock_bh(&pdev->req_list_spinlock); + + if (!found) { + TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, + "req(%p) from firmware can't be found in the list\n", req); + return; + } + do { htt_t2h_dbg_stats_hdr_parse( stats_info_list, &type, &status, &length, &stats_data); @@ -2184,10 +2229,16 @@ ol_txrx_fw_stats_handler( } while (1); if (! more) { - if (req->base.wait.blocking) { - adf_os_mutex_release(pdev->osdev, req->base.wait.sem_ptr); + adf_os_spin_lock_bh(&pdev->req_list_spinlock); + TAILQ_FOREACH(tmp, &pdev->req_list, req_list_elem) { + if (req == tmp) { + TAILQ_REMOVE(&pdev->req_list, req, req_list_elem); + pdev->req_list_depth--; + adf_os_mem_free(req); + break; + } } - adf_os_mem_free(req); + adf_os_spin_unlock_bh(&pdev->req_list_spinlock); } } diff --git a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx_types.h b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx_types.h index ff889caff7c04..2873e9d3c3215 100644 --- a/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx_types.h +++ b/drivers/staging/qcacld-2.0/CORE/CLD_TXRX/TXRX/ol_txrx_types.h @@ -544,6 +544,10 @@ struct ol_txrx_pdev_t { /* ol_txrx_vdev list */ TAILQ_HEAD(, ol_txrx_vdev_t) vdev_list; + TAILQ_HEAD(, ol_txrx_stats_req_internal) req_list; + int req_list_depth; + adf_os_spinlock_t req_list_spinlock; + /* peer ID to peer object map (array of pointers to peer objects) */ struct ol_txrx_peer_t **peer_id_to_obj_map; diff --git a/drivers/staging/qcacld-2.0/CORE/HDD/src/wlan_hdd_assoc.c b/drivers/staging/qcacld-2.0/CORE/HDD/src/wlan_hdd_assoc.c index 28fb6b23e96e2..d2543592861e7 100644 --- a/drivers/staging/qcacld-2.0/CORE/HDD/src/wlan_hdd_assoc.c +++ b/drivers/staging/qcacld-2.0/CORE/HDD/src/wlan_hdd_assoc.c @@ -4418,6 +4418,7 @@ static tANI_S32 hdd_ProcessGENIE(hdd_adapter_t *pAdapter, tDot11fIERSN dot11RSNIE; tDot11fIEWPA dot11WPAIE; tANI_U32 i; + tANI_U32 status; tANI_U8 *pRsnIe; tANI_U16 RSNIeLen; tPmkidCacheInfo PMKIDCache[4]; // Local transfer memory @@ -4443,10 +4444,17 @@ static tANI_S32 hdd_ProcessGENIE(hdd_adapter_t *pAdapter, pRsnIe = gen_ie + 2; RSNIeLen = gen_ie_len - 2; // Unpack the RSN IE - dot11fUnpackIeRSN((tpAniSirGlobal) halHandle, + status = dot11fUnpackIeRSN((tpAniSirGlobal) halHandle, pRsnIe, RSNIeLen, &dot11RSNIE); + if (DOT11F_FAILED(status)) + { + hddLog(LOGE, + FL("Parse failure in hdd_ProcessGENIE (0x%08x)"), + status); + return -EINVAL; + } // Copy out the encryption and authentication types hddLog(LOG1, FL("%s: pairwise cipher suite count: %d"), __func__, dot11RSNIE.pwise_cipher_suite_count ); diff --git a/drivers/staging/qcacld-2.0/CORE/MAC/inc/sirMacProtDef.h b/drivers/staging/qcacld-2.0/CORE/MAC/inc/sirMacProtDef.h index 30bb3ca957ecc..ab5487456b62c 100644 --- a/drivers/staging/qcacld-2.0/CORE/MAC/inc/sirMacProtDef.h +++ b/drivers/staging/qcacld-2.0/CORE/MAC/inc/sirMacProtDef.h @@ -586,6 +586,7 @@ #define SIR_MAC_MAX_NUM_OF_DEFAULT_KEYS 4 #define SIR_MAC_KEY_LENGTH 13 // WEP Maximum key length size #define SIR_MAC_AUTH_CHALLENGE_LENGTH 253 +#define SIR_MAC_SAP_AUTH_CHALLENGE_LENGTH 128 #define SIR_MAC_WEP_IV_LENGTH 4 #define SIR_MAC_WEP_ICV_LENGTH 4 #define SIR_MAC_CHALLENGE_ID_LEN 2 diff --git a/drivers/staging/qcacld-2.0/CORE/MAC/src/cfg/cfgProcMsg.c b/drivers/staging/qcacld-2.0/CORE/MAC/src/cfg/cfgProcMsg.c index 943ba48cab560..947f848401406 100644 --- a/drivers/staging/qcacld-2.0/CORE/MAC/src/cfg/cfgProcMsg.c +++ b/drivers/staging/qcacld-2.0/CORE/MAC/src/cfg/cfgProcMsg.c @@ -2712,7 +2712,8 @@ ProcSetReqInternal(tpAniSirGlobal pMac, tANI_U16 length, tANI_U32 *pParam, tANI_ // Process string parameter else { - if (valueLenRoundedUp4 > length) + if ((valueLenRoundedUp4 > length) || + (valueLen > CFG_MAX_STR_LEN)) { PELOGE(cfgLog(pMac, LOGE, FL("Invalid string length %d" "in set param %d (tot %d)"), valueLen, diff --git a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessAuthFrame.c b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessAuthFrame.c index 05591d0494441..af5411e18eaf6 100644 --- a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessAuthFrame.c +++ b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessAuthFrame.c @@ -290,7 +290,8 @@ limProcessAuthFrame(tpAniSirGlobal pMac, tANI_U8 *pRxPacketInfo, tpPESession pse goto free; } - if (frameLen < LIM_ENCR_AUTH_BODY_LEN) + if ((frameLen < LIM_ENCR_AUTH_BODY_LEN_SAP) || + (frameLen > LIM_ENCR_AUTH_BODY_LEN_SAP)) { // Log error limLog(pMac, LOGE, @@ -966,9 +967,9 @@ limProcessAuthFrame(tpAniSirGlobal pMac, tANI_U8 *pRxPacketInfo, tpPESession pse pAuthNode->fTimerStarted = 1; /* - * get random bytes and use as challenge text. + * get random bytes and use as challenge text */ - if( !VOS_IS_STATUS_SUCCESS( vos_rand_get_bytes( 0, (tANI_U8 *)challengeTextArray, SIR_MAC_AUTH_CHALLENGE_LENGTH ) ) ) + if( !VOS_IS_STATUS_SUCCESS( vos_rand_get_bytes( 0, (tANI_U8 *)challengeTextArray, SIR_MAC_SAP_AUTH_CHALLENGE_LENGTH ) ) ) { limLog(pMac, LOGE,FL("Challenge text preparation failed in limProcessAuthFrame")); goto free; @@ -990,11 +991,12 @@ limProcessAuthFrame(tpAniSirGlobal pMac, tANI_U8 *pRxPacketInfo, tpPESession pse pRxAuthFrameBody->authTransactionSeqNumber + 1; authFrame->authStatusCode = eSIR_MAC_SUCCESS_STATUS; + authFrame->type = SIR_MAC_CHALLENGE_TEXT_EID; - authFrame->length = SIR_MAC_AUTH_CHALLENGE_LENGTH; + authFrame->length = SIR_MAC_SAP_AUTH_CHALLENGE_LENGTH; vos_mem_copy(authFrame->challengeText, pAuthNode->challengeText, - SIR_MAC_AUTH_CHALLENGE_LENGTH); + SIR_MAC_SAP_AUTH_CHALLENGE_LENGTH); limSendAuthMgmtFrame( pMac, authFrame, @@ -1596,7 +1598,7 @@ limProcessAuthFrame(tpAniSirGlobal pMac, tANI_U8 *pRxPacketInfo, tpPESession pse if (vos_mem_compare(pRxAuthFrameBody->challengeText, pAuthNode->challengeText, - SIR_MAC_AUTH_CHALLENGE_LENGTH)) + SIR_MAC_SAP_AUTH_CHALLENGE_LENGTH)) { /// Challenge match. STA is autheticated ! diff --git a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessProbeReqFrame.c b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessProbeReqFrame.c index 4cef79044b99d..b6bb3ee09404f 100644 --- a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessProbeReqFrame.c +++ b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limProcessProbeReqFrame.c @@ -742,6 +742,10 @@ limSendSmeProbeReqInd(tpAniSirGlobal pMac, MTRACE(macTrace(pMac, TRACE_CODE_TX_SME_MSG, psessionEntry->peSessionId, msgQ.type)); + + if (ProbeReqIELen > sizeof(pSirSmeProbeReqInd->WPSPBCProbeReq.probeReqIE)) + ProbeReqIELen = sizeof(pSirSmeProbeReqInd->WPSPBCProbeReq.probeReqIE); + pSirSmeProbeReqInd->WPSPBCProbeReq.probeReqIELen = (tANI_U16)ProbeReqIELen; vos_mem_copy(pSirSmeProbeReqInd->WPSPBCProbeReq.probeReqIE, pProbeReqIE, ProbeReqIELen); diff --git a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSecurityUtils.h b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSecurityUtils.h index 4fb961c3cebb7..3dd987b1fa9ad 100644 --- a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSecurityUtils.h +++ b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSecurityUtils.h @@ -48,6 +48,11 @@ SIR_MAC_WEP_ICV_LENGTH + \ SIR_MAC_CHALLENGE_ID_LEN) +#define LIM_ENCR_AUTH_BODY_LEN_SAP (SIR_MAC_SAP_AUTH_CHALLENGE_LENGTH + \ + SIR_MAC_CHALLENGE_ID_LEN + \ + SIR_MAC_AUTH_FRAME_INFO_LEN + \ + SIR_MAC_WEP_IV_LENGTH + \ + SIR_MAC_WEP_ICV_LENGTH) struct tLimPreAuthNode; tANI_U8 limIsAuthAlgoSupported(tpAniSirGlobal, tAniAuthType, tpPESession); diff --git a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSendManagementFrames.c b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSendManagementFrames.c index ac0def36471c6..162d7ed91acf2 100644 --- a/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSendManagementFrames.c +++ b/drivers/staging/qcacld-2.0/CORE/MAC/src/pe/lim/limSendManagementFrames.c @@ -3516,10 +3516,11 @@ limSendAuthMgmtFrame(tpAniSirGlobal pMac, * transaction number, status code and 128 bytes * for challenge text. */ - + bodyLen = SIR_MAC_AUTH_FRAME_INFO_LEN + + SIR_MAC_SAP_AUTH_CHALLENGE_LENGTH + + SIR_MAC_CHALLENGE_ID_LEN; frameLen = sizeof(tSirMacMgmtHdr) + - sizeof(tSirMacAuthFrame); - bodyLen = sizeof(tSirMacAuthFrameBody); + bodyLen; } break; diff --git a/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_htt_rx_api.h b/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_htt_rx_api.h index b8e98250a4be7..ba043dbdc4e65 100644 --- a/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_htt_rx_api.h +++ b/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_htt_rx_api.h @@ -833,8 +833,8 @@ void htt_rx_frag_ind_flush_seq_num_range( htt_pdev_handle pdev, adf_nbuf_t rx_frag_ind_msg, - int *seq_num_start, - int *seq_num_end); + u_int16_t *seq_num_start, + u_int16_t *seq_num_end); /** * @brief Return the HL rx desc size * @param pdev - the HTT instance the rx data was received on diff --git a/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_dbg.h b/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_dbg.h index 7309db33dc865..517bf5f4b3175 100644 --- a/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_dbg.h +++ b/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_dbg.h @@ -76,6 +76,13 @@ struct ol_txrx_stats_req { } wait; }; +struct ol_txrx_stats_req_internal { + struct ol_txrx_stats_req base; + TAILQ_ENTRY(ol_txrx_stats_req_internal) req_list_elem; + int serviced; /* state of this request */ + int offset; +}; + #ifndef TXRX_DEBUG_LEVEL #define TXRX_DEBUG_LEVEL 0 /* no debug info */ #endif diff --git a/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_htt_api.h b/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_htt_api.h index 0baafe0f6bdec..8d42b0f061146 100644 --- a/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_htt_api.h +++ b/drivers/staging/qcacld-2.0/CORE/SERVICES/COMMON/ol_txrx_htt_api.h @@ -617,8 +617,8 @@ ol_rx_pn_ind_handler( ol_txrx_pdev_handle pdev, u_int16_t peer_id, u_int8_t tid, - int seq_num_start, - int seq_num_end, + u_int16_t seq_num_start, + u_int16_t seq_num_end, u_int8_t pn_ie_cnt, u_int8_t *pn_ie); diff --git a/drivers/staging/qcacld-2.0/CORE/SERVICES/HIF/USB/hif_usb.c b/drivers/staging/qcacld-2.0/CORE/SERVICES/HIF/USB/hif_usb.c index a6a88bf2754c8..caae8f0e3c2a5 100644 --- a/drivers/staging/qcacld-2.0/CORE/SERVICES/HIF/USB/hif_usb.c +++ b/drivers/staging/qcacld-2.0/CORE/SERVICES/HIF/USB/hif_usb.c @@ -245,7 +245,8 @@ static A_STATUS HIFSend_internal(HIF_DEVICE *hifDevice, a_uint8_t PipeID, int usb_status; int i; struct HIFSendContext *pSendContext; - int frag_count = 0, head_data_len, tmp_frag_count = 0; + uint8_t frag_count; + uint32_t head_data_len, tmp_frag_count = 0; unsigned char *pData; AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT, ("+%s pipe : %d, buf:0x%p\n", @@ -254,8 +255,15 @@ static A_STATUS HIFSend_internal(HIF_DEVICE *hifDevice, a_uint8_t PipeID, a_mem_trace(buf); frag_count = adf_nbuf_get_num_frags(buf); - if (frag_count > 1) { /* means have extra fragment buf in skb */ - /* header data length should be total sending length substract + if (frag_count == 1) { + /* + * | HIFSendContext | netbuf->data + */ + head_data_len = sizeof(struct HIFSendContext); + } else if ((frag_count - 1) <= CVG_NBUF_MAX_EXTRA_FRAGS) { + /* + * means have extra fragment buf in skb + * header data length should be total sending length substract * internal data length of netbuf * | HIFSendContext | fragments except internal buffer | * netbuf->data @@ -268,10 +276,12 @@ static A_STATUS HIFSend_internal(HIF_DEVICE *hifDevice, a_uint8_t PipeID, tmp_frag_count = tmp_frag_count + 1; } } else { - /* - * | HIFSendContext | netbuf->data - */ - head_data_len = sizeof(struct HIFSendContext); + /* Extra fragments overflow */ + AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ( + "%s Extra fragments count overflow : %d\n", + __func__, frag_count)); + status = A_ERROR; + goto exit; } /* Check whether head room is enough to save extra head data */ @@ -366,6 +376,7 @@ static A_STATUS HIFSend_internal(HIF_DEVICE *hifDevice, a_uint8_t PipeID, } while (FALSE); +exit: if (A_FAILED(status) && (status != A_NO_RESOURCE)) { AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("athusb send failed %d\n", status)); diff --git a/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.c b/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.c index 455df22a8810d..11aeea337a431 100644 --- a/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.c +++ b/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2016 The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2018 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -498,7 +498,7 @@ static bool wma_is_vdev_in_ap_mode(tp_wma_handle wma, u_int8_t vdev_id) { struct wma_txrx_node *intf = wma->interfaces; - if (vdev_id > wma->max_bssid) { + if (vdev_id >= wma->max_bssid) { WMA_LOGP("%s: Invalid vdev_id %hu", __func__, vdev_id); VOS_ASSERT(0); return false; @@ -524,7 +524,7 @@ static bool wma_is_vdev_in_ibss_mode(tp_wma_handle wma, u_int8_t vdev_id) { struct wma_txrx_node *intf = wma->interfaces; - if (vdev_id > wma->max_bssid) { + if (vdev_id >= wma->max_bssid) { WMA_LOGP("%s: Invalid vdev_id %hu", __func__, vdev_id); VOS_ASSERT(0); return false; @@ -1141,6 +1141,11 @@ static int wma_vdev_start_resp_handler(void *handle, u_int8_t *cmd_param_info, return -EINVAL; } + if (resp_event->vdev_id >= wma->max_bssid) { + WMA_LOGE("Invalid vdev id received from firmware"); + return -EINVAL; + } + if (wma_is_vdev_in_ap_mode(wma, resp_event->vdev_id)) { adf_os_spin_lock_bh(&wma->dfs_ic->chan_lock); wma->dfs_ic->disable_phy_err_processing = false; @@ -1295,9 +1300,15 @@ static int wma_vdev_start_rsp_ind(tp_wma_handle wma, u_int8_t *buf) return -EINVAL; } + if (resp_event->vdev_id >= wma->max_bssid) { + WMA_LOGE("%s: received invalid vdev_id %d", + __func__, resp_event->vdev_id); + return -EINVAL; + } + iface = &wma->interfaces[resp_event->vdev_id]; - if ((resp_event->vdev_id <= wma->max_bssid) && + if ((resp_event->vdev_id < wma->max_bssid) && (adf_os_atomic_read( &wma->interfaces[resp_event->vdev_id].vdev_restart_params.hidden_ssid_restart_in_progress)) && (wma_is_vdev_in_ap_mode(wma, resp_event->vdev_id) == true)) { @@ -1898,7 +1909,7 @@ static void wma_delete_all_ibss_peers(tp_wma_handle wma, A_UINT32 vdev_id) ol_txrx_vdev_handle vdev; ol_txrx_peer_handle peer, temp; - if (!wma || vdev_id > wma->max_bssid) + if (!wma || vdev_id >= wma->max_bssid) return; vdev = wma->interfaces[vdev_id].handle; @@ -1940,7 +1951,7 @@ static void wma_delete_all_ap_remote_peers(tp_wma_handle wma, A_UINT32 vdev_id) ol_txrx_vdev_handle vdev; ol_txrx_peer_handle peer, temp; - if (!wma || vdev_id > wma->max_bssid) + if (!wma || vdev_id >= wma->max_bssid) return; vdev = wma->interfaces[vdev_id].handle; @@ -2138,7 +2149,7 @@ static int wma_vdev_stop_ind(tp_wma_handle wma, u_int8_t *buf) resp_event = (wmi_vdev_stopped_event_fixed_param *)buf; - if ((resp_event->vdev_id <= wma->max_bssid) && + if ((resp_event->vdev_id < wma->max_bssid) && (adf_os_atomic_read(&wma->interfaces[resp_event->vdev_id].vdev_restart_params.hidden_ssid_restart_in_progress)) && ((wma->interfaces[resp_event->vdev_id].type == WMI_VDEV_TYPE_AP) && (wma->interfaces[resp_event->vdev_id].sub_type == 0))) { @@ -2177,7 +2188,7 @@ static int wma_vdev_stop_ind(tp_wma_handle wma, u_int8_t *buf) tpDeleteBssParams params = (tpDeleteBssParams)req_msg->user_data; struct beacon_info *bcn; - if (resp_event->vdev_id > wma->max_bssid) { + if (resp_event->vdev_id >= wma->max_bssid) { WMA_LOGE("%s: Invalid vdev_id %d", __func__, resp_event->vdev_id); vos_mem_free(params); @@ -3929,7 +3940,7 @@ static int wma_extscan_hotlist_match_event_handler(void *handle, wmi_extscan_wlan_descriptor *src_hotlist; uint32_t numap; int j, ap_found = 0; - + uint32_t buf_len; tpAniSirGlobal pMac = (tpAniSirGlobal )vos_get_context( VOS_MODULE_ID_PE, wma->vos_context); if (!pMac) { @@ -3959,6 +3970,13 @@ static int wma_extscan_hotlist_match_event_handler(void *handle, __func__, numap); numap = WMA_EXTSCAN_MAX_HOTLIST_ENTRIES; } + buf_len = sizeof(wmi_extscan_hotlist_match_event_fixed_param) + + WMI_TLV_HDR_SIZE + + (numap * sizeof(wmi_extscan_wlan_descriptor)); + if (buf_len > len) { + WMA_LOGE("Invalid buf len from FW %d numap %d", len, numap); + return -EINVAL; + } dest_hotlist = vos_mem_malloc(sizeof(*dest_hotlist) + sizeof(*dest_ap) * numap); if (!dest_hotlist) { @@ -3994,6 +4012,11 @@ static int wma_extscan_hotlist_match_event_handler(void *handle, dest_ap->ieLength = src_hotlist-> ie_length; WMI_MAC_ADDR_TO_CHAR_ARRAY(&src_hotlist->bssid, dest_ap->bssid); + if (src_hotlist->ssid.ssid_len > SIR_MAC_MAX_SSID_LENGTH) { + WMA_LOGE("%s Invalid SSID len %d, truncating", + __func__, src_hotlist->ssid.ssid_len); + src_hotlist->ssid.ssid_len = SIR_MAC_MAX_SSID_LENGTH; + } vos_mem_copy(dest_ap->ssid, src_hotlist->ssid.ssid, src_hotlist->ssid.ssid_len); dest_ap->ssid[src_hotlist->ssid.ssid_len] = '\0'; @@ -4168,6 +4191,13 @@ static int wma_group_num_bss_to_scan_id(const u_int8_t *cmd_param_info, WMI_MAC_ADDR_TO_CHAR_ARRAY(&src_hotlist->bssid, ap->bssid); + if (src_hotlist->ssid.ssid_len > + SIR_MAC_MAX_SSID_LENGTH) { + WMA_LOGD("%s Invalid SSID len %d, truncating", + __func__, src_hotlist->ssid.ssid_len); + src_hotlist->ssid.ssid_len = + SIR_MAC_MAX_SSID_LENGTH; + } vos_mem_copy(ap->ssid, src_hotlist->ssid.ssid, src_hotlist->ssid.ssid_len); ap->ssid[src_hotlist->ssid.ssid_len] = '\0'; @@ -4476,9 +4506,13 @@ static int wma_passpoint_match_event_handler(void *handle, WMA_SVC_MSG_MAX_SIZE) { WMA_LOGE("IE Length: %d or ANQP Length: %d is huge", event->ie_length, event->anqp_length); - VOS_ASSERT(0); return -EINVAL; } + if (event->ssid.ssid_len > SIR_MAC_MAX_SSID_LENGTH) { + WMA_LOGD("%s: Invalid ssid len %d, truncating", + __func__, event->ssid.ssid_len); + event->ssid.ssid_len = SIR_MAC_MAX_SSID_LENGTH; + } dest_match = vos_mem_malloc(sizeof(*dest_match) + event->ie_length + event->anqp_length); if (!dest_match) { @@ -4570,6 +4604,11 @@ static int wma_unified_link_iface_stats_event_handler(void *handle, WMA_LOGA("%s: Invalid param_tlvs for Iface Stats", __func__); return -EINVAL; } + if (link_stats->num_ac > WIFI_AC_MAX) { + WMA_LOGE("%s: Excess data received from firmware num_ac %d", + __func__, link_stats->num_ac); + return -EINVAL; + } link_stats_size = sizeof(tSirWifiIfaceStat); iface_info_size = sizeof(tSirWifiInterfaceInfo); @@ -5018,8 +5057,23 @@ static int wma_unified_link_radio_stats_event_handler(void *handle, return -EINVAL; } + if (radio_stats->num_channels > + NUM_2_4GHZ_CHANNELS + NUM_5GHZ_CHANNELS) { + WMA_LOGE("%s: Too many channels %d", + __func__, radio_stats->num_channels); + return -EINVAL; + } + radio_stats_size = sizeof(tSirWifiRadioStat); chan_stats_size = sizeof(tSirWifiChannelStats); + + if (fixed_param->num_radio > + (UINT_MAX - sizeof(*link_stats_results)) / radio_stats_size) { + WMA_LOGE("excess num_radio %d is leading to int overflow", + fixed_param->num_radio); + return -EINVAL; + } + link_stats_results_size = sizeof(*link_stats_results) + radio_stats_size + (radio_stats->num_channels * chan_stats_size); @@ -5327,6 +5381,12 @@ static void wma_send_bcn_buf_ll(tp_wma_handle wma, WMA_LOGE("%s: Invalid beacon buffer", __func__); return; } + if (WMI_UNIFIED_NOA_ATTR_NUM_DESC_GET(p2p_noa_info) > + WMI_P2P_MAX_NOA_DESCRIPTORS) { + WMA_LOGE("%s: Too many descriptors %d", __func__, + WMI_UNIFIED_NOA_ATTR_NUM_DESC_GET(p2p_noa_info)); + return; + } wmi_buf = wmi_buf_alloc(wma->wmi_handle, sizeof(*cmd)); if (!wmi_buf) { @@ -5953,6 +6013,12 @@ static int wma_p2p_noa_event_handler(void *handle, u_int8_t *event, u_int32_t le descriptors = WMI_UNIFIED_NOA_ATTR_NUM_DESC_GET(p2p_noa_info); noa_ie.num_descriptors = (u_int8_t)descriptors; + if (noa_ie.num_descriptors > WMA_MAX_NOA_DESCRIPTORS) { + WMA_LOGD("Sizing down the no of desc %d to max", + noa_ie.num_descriptors); + noa_ie.num_descriptors = WMA_MAX_NOA_DESCRIPTORS; + } + WMA_LOGI("%s: index %u, oppPs %u, ctwindow %u, " "num_descriptors = %u", __func__, noa_ie.index, noa_ie.oppPS, noa_ie.ctwindow, noa_ie.num_descriptors); @@ -6269,7 +6335,8 @@ static int wma_nan_rsp_event_handler(void *handle, u_int8_t *event_buf, alloc_len = sizeof(tSirNanEvent); alloc_len += nan_rsp_event_hdr->data_len; if (nan_rsp_event_hdr->data_len > ((WMA_SVC_MSG_MAX_SIZE - - sizeof(*nan_rsp_event_hdr)) / sizeof(u_int8_t))) { + sizeof(*nan_rsp_event_hdr)) / sizeof(u_int8_t)) || + nan_rsp_event_hdr->data_len > param_buf->num_data) { WMA_LOGE("excess data length:%d", nan_rsp_event_hdr->data_len); VOS_ASSERT(0); return -EINVAL; @@ -6594,6 +6661,7 @@ static int wma_roam_synch_event_handler(void *handle, u_int8_t *event, u_int32_t wmi_key_material *key = NULL; int size=0; tSirRoamOffloadSynchInd *pRoamOffloadSynchInd; + uint32_t roam_synch_data_len; WMA_LOGD("LFR3:%s", __func__); if (!event) { @@ -6613,35 +6681,56 @@ static int wma_roam_synch_event_handler(void *handle, u_int8_t *event, u_int32_t return -EINVAL; } + if (synch_event->vdev_id >= wma->max_bssid) { + WMA_LOGE("%s: received invalid vdev_id %d", + __func__, synch_event->vdev_id); + return -EINVAL; + } + if(wma->interfaces[synch_event->vdev_id].roam_synch_in_progress == VOS_TRUE) { WMA_LOGE("%s: Ignoring RSI since one is already in progress", __func__); return -EINVAL; } + + WMA_LOGD("synch payload: LEN bcn:%d, req:%d, rsp:%d", + synch_event->bcn_probe_rsp_len, + synch_event->reassoc_req_len, + synch_event->reassoc_rsp_len); + + if (synch_event->bcn_probe_rsp_len > WMA_SVC_MSG_MAX_SIZE) + return -EINVAL; + if (synch_event->reassoc_rsp_len > + (WMA_SVC_MSG_MAX_SIZE - synch_event->bcn_probe_rsp_len)) + return -EINVAL; + if (synch_event->reassoc_req_len > + WMA_SVC_MSG_MAX_SIZE - (synch_event->bcn_probe_rsp_len + + synch_event->reassoc_rsp_len)) + return -EINVAL; + + roam_synch_data_len = synch_event->bcn_probe_rsp_len + + synch_event->reassoc_rsp_len + + synch_event->reassoc_req_len; /* - * All below length fields are unsigned and hence positive numbers. - * Maximum number during the addition would be (3 * MAX_LIMIT(UINT32) + - * few fixed fields). + * Below is the check for the entire size of the message received from' + * the firmware. */ - if ((sizeof(*synch_event) + synch_event->bcn_probe_rsp_len + - synch_event->reassoc_rsp_len + - sizeof(wmi_channel) + sizeof(wmi_key_material) + - sizeof(uint32_t)) > WMA_SVC_MSG_MAX_SIZE) { - WMA_LOGE("excess synch payload: LEN bcn:%d, rsp:%d", - synch_event->bcn_probe_rsp_len, - synch_event->reassoc_rsp_len); - VOS_ASSERT(0); + if (roam_synch_data_len > WMA_SVC_MSG_MAX_SIZE - + (sizeof(*synch_event) + sizeof(wmi_channel) + + sizeof(wmi_key_material) + sizeof(uint32_t))) return -EINVAL; - } + + if (sizeof(tSirRoamOffloadSynchInd) > + (WMA_SVC_MSG_MAX_SIZE - roam_synch_data_len)) + return -EINVAL; + roam_synch_data_len += sizeof(tSirRoamOffloadSynchInd); adf_os_spin_lock_bh(&wma->roam_synch_lock); wma->interfaces[synch_event->vdev_id].roam_synch_in_progress = VOS_TRUE; adf_os_spin_unlock_bh(&wma->roam_synch_lock); - len = sizeof(tSirRoamOffloadSynchInd) + - synch_event->bcn_probe_rsp_len + - synch_event->reassoc_rsp_len; - pRoamOffloadSynchInd = (tSirRoamOffloadSynchInd *)vos_mem_malloc(len); + pRoamOffloadSynchInd = + (tSirRoamOffloadSynchInd *)vos_mem_malloc(roam_synch_data_len); if (!pRoamOffloadSynchInd) { WMA_LOGE("%s: failed to allocate memory for roam_synch_event", __func__); return -ENOMEM; @@ -9722,7 +9811,7 @@ VOS_STATUS wma_start_scan(tp_wma_handle wma_handle, int len; tSirScanOffloadEvent *scan_event; - if (scan_req->sessionId > wma_handle->max_bssid) { + if (scan_req->sessionId >= wma_handle->max_bssid) { WMA_LOGE("%s: Invalid vdev_id %d, msg_type : 0x%x", __func__, scan_req->sessionId, msg_type); goto error1; @@ -12693,7 +12782,7 @@ void wma_vdev_resp_timer(void *data) struct beacon_info *bcn; struct wma_txrx_node *iface; - if (tgt_req->vdev_id > wma->max_bssid) { + if (tgt_req->vdev_id >= wma->max_bssid) { WMA_LOGE("%s: Invalid vdev_id %d", __func__, tgt_req->vdev_id); vos_mem_free(params); @@ -21430,19 +21519,18 @@ static int wma_wow_wakeup_host_event(void *handle, u_int8_t *event, wake_info = param_buf->fixed_param; - if ((wake_info->wake_reason != WOW_REASON_UNSPECIFIED) || - (wake_info->wake_reason == WOW_REASON_UNSPECIFIED && - !wmi_get_runtime_pm_inprogress(wma->wmi_handle))) { + if (!wmi_get_runtime_pm_inprogress(wma->wmi_handle)) { if (wake_info->vdev_id >= wma->max_bssid) { WMA_LOGE("%s: received invalid vdev_id %d", __func__, wake_info->vdev_id); return -EINVAL; } - WMA_LOGA("WOW wakeup host event received (reason: %s(%d)) for vdev %d", + + WMA_LOGA("WOW (%d) %s vdev:%d", + wake_info->wake_reason, wma_wow_wake_reason_str(wake_info->wake_reason, wma), wake_info->wake_reason, wake_info->vdev_id); - vos_wow_wakeup_host_event(wake_info->wake_reason); } vos_event_set(&wma->wma_resume_event); @@ -21494,6 +21582,12 @@ static int wma_wow_wakeup_host_event(void *handle, u_int8_t *event, #ifdef FEATURE_WLAN_SCAN_PNO case WOW_REASON_NLOD: wma_wow_wake_up_stats(wma, NULL, 0, WOW_REASON_NLOD); + if (wake_info->vdev_id >= wma->max_bssid) { + WMA_LOGE("%s: received invalid vdev_id %d", + __func__, wake_info->vdev_id); + return -EINVAL; + } + node = &wma->interfaces[wake_info->vdev_id]; if (node) { WMA_LOGD("NLO match happened"); @@ -23157,7 +23251,7 @@ static VOS_STATUS wma_wow_enter(tp_wma_handle wma, WMA_LOGD("wow enable req received for vdev id: %d", info->sessionId); - if (info->sessionId > wma->max_bssid) { + if (info->sessionId >= wma->max_bssid) { WMA_LOGE("Invalid vdev id (%d)", info->sessionId); vos_mem_free(info); return VOS_STATUS_E_INVAL; @@ -23184,7 +23278,7 @@ static VOS_STATUS wma_wow_exit(tp_wma_handle wma, WMA_LOGD("wow disable req received for vdev id: %d", info->sessionId); - if (info->sessionId > wma->max_bssid) { + if (info->sessionId >= wma->max_bssid) { WMA_LOGE("Invalid vdev id (%d)", info->sessionId); vos_mem_free(info); return VOS_STATUS_E_INVAL; @@ -23217,7 +23311,7 @@ static VOS_STATUS wma_suspend_req(tp_wma_handle wma, tpSirWlanSuspendParam info) wma->no_of_suspend_ind++; - if (info->sessionId > wma->max_bssid) { + if (info->sessionId >= wma->max_bssid) { WMA_LOGE("Invalid vdev id (%d)", info->sessionId); vos_mem_free(info); return VOS_STATUS_E_INVAL; @@ -31720,6 +31814,11 @@ static int wma_nlo_match_evt_handler(void *handle, u_int8_t *event, nlo_event = param_buf->fixed_param; WMA_LOGD("PNO match event received for vdev %d", nlo_event->vdev_id); + if (nlo_event->vdev_id >= wma->max_bssid) { + WMA_LOGE("Invalid vdev id in the NLO event %d", + nlo_event->vdev_id); + return -EINVAL; + } node = &wma->interfaces[nlo_event->vdev_id]; if (node) @@ -31868,7 +31967,8 @@ static int wma_mcc_vdev_tx_pause_evt_handler(void *handle, u_int8_t *event, /* FW mapped vdev from ID * vdev_map = (1 << vdev_id) * So, host should unmap to ID */ - for (vdev_id = 0; vdev_map != 0; vdev_id++) + for (vdev_id = 0; vdev_map != 0 && vdev_id < wma->max_bssid; + vdev_id++) { if (!(vdev_map & 0x1)) { @@ -35071,6 +35171,14 @@ wma_process_utf_event(WMA_HANDLE handle, currentSeq); } + if ((datalen > MAX_UTF_EVENT_LENGTH) || + (wma_handle->utf_event_info.offset > + (MAX_UTF_EVENT_LENGTH - datalen))) { + WMA_LOGE("Excess data from firmware, offset:%zu, len:%d", + wma_handle->utf_event_info.offset, datalen); + return -EINVAL; + } + memcpy(&wma_handle->utf_event_info.data[wma_handle->utf_event_info.offset], &data[sizeof(segHdrInfo)], datalen); diff --git a/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.h b/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.h index f8f2028f79df4..c210ec094fa6b 100644 --- a/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.h +++ b/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma.h @@ -101,6 +101,8 @@ #define FRAGMENT_SIZE 3072 +#define WMA_MAX_MGMT_MPDU_LEN 2000 + #define WMA_INVALID_VDEV_ID 0xFF #define MAX_MEM_CHUNKS 32 #define WMA_MAX_VDEV_SIZE 20 diff --git a/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma_nan_datapath.c b/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma_nan_datapath.c index 2d2909e6b3912..f5ea7ac92498f 100644 --- a/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma_nan_datapath.c +++ b/drivers/staging/qcacld-2.0/CORE/SERVICES/WMA/wma_nan_datapath.c @@ -1138,7 +1138,7 @@ void wma_delete_all_nan_remote_peers(tp_wma_handle wma, uint32_t vdev_id) ol_txrx_vdev_handle vdev; ol_txrx_peer_handle peer, temp; - if (!wma || vdev_id > wma->max_bssid) + if (!wma || vdev_id >= wma->max_bssid) return; vdev = wma->interfaces[vdev_id].handle; diff --git a/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/linux_ac.c b/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/linux_ac.c index a3c470b678a0d..ed6db1766188b 100644 --- a/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/linux_ac.c +++ b/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/linux_ac.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2016 The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2018 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -78,6 +78,8 @@ static struct ath_pktlog_info *g_pktlog_info; static struct proc_dir_entry *g_pktlog_pde; +static DEFINE_MUTEX(proc_mutex); + static int pktlog_attach(struct ol_softc *sc); static void pktlog_detach(struct ol_softc *sc); static int pktlog_open(struct inode *i, struct file *f); @@ -226,9 +228,11 @@ ATH_SYSCTL_DECL(ath_sysctl_pktlog_enable, ctl, write, filp, buffer, lenp, ol_ath_generic_softc_handle scn; struct ol_pktlog_dev_t *pl_dev; + mutex_lock(&proc_mutex); scn = (ol_ath_generic_softc_handle) ctl->extra1; if (!scn) { + mutex_unlock(&proc_mutex); printk("%s: Invalid scn context\n", __func__); ASSERT(0); return -EINVAL; @@ -237,6 +241,7 @@ ATH_SYSCTL_DECL(ath_sysctl_pktlog_enable, ctl, write, filp, buffer, lenp, pl_dev = get_pl_handle((struct ol_softc *)scn); if (!pl_dev) { + mutex_unlock(&proc_mutex); printk("%s: Invalid pktlog context\n", __func__); ASSERT(0); return -ENODEV; @@ -266,6 +271,7 @@ ATH_SYSCTL_DECL(ath_sysctl_pktlog_enable, ctl, write, filp, buffer, lenp, ctl->data = NULL; ctl->maxlen = 0; + mutex_unlock(&proc_mutex); return ret; } @@ -283,9 +289,11 @@ ATH_SYSCTL_DECL(ath_sysctl_pktlog_size, ctl, write, filp, buffer, lenp, ol_ath_generic_softc_handle scn; struct ol_pktlog_dev_t *pl_dev; + mutex_lock(&proc_mutex); scn = (ol_ath_generic_softc_handle) ctl->extra1; if (!scn) { + mutex_unlock(&proc_mutex); printk("%s: Invalid scn context\n", __func__); ASSERT(0); return -EINVAL; @@ -294,6 +302,7 @@ ATH_SYSCTL_DECL(ath_sysctl_pktlog_size, ctl, write, filp, buffer, lenp, pl_dev = get_pl_handle((struct ol_softc *)scn); if (!pl_dev) { + mutex_unlock(&proc_mutex); printk("%s: Invalid pktlog handle\n", __func__); ASSERT(0); return -ENODEV; @@ -318,6 +327,7 @@ ATH_SYSCTL_DECL(ath_sysctl_pktlog_size, ctl, write, filp, buffer, lenp, ctl->data = NULL; ctl->maxlen = 0; + mutex_unlock(&proc_mutex); return ret; } diff --git a/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/pktlog_internal.c b/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/pktlog_internal.c index 3db0b8758af21..79c7dc81915be 100644 --- a/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/pktlog_internal.c +++ b/drivers/staging/qcacld-2.0/CORE/UTILS/PKTLOG/pktlog_internal.c @@ -307,6 +307,10 @@ process_tx_info(struct ol_txrx_pdev_t *txrx_pdev, */ txctl_log.priv.frm_hdr = frm_hdr; adf_os_assert(txctl_log.priv.txdesc_ctl); + adf_os_assert(pl_hdr.size < sizeof(txctl_log.priv.txdesc_ctl)); + pl_hdr.size = (pl_hdr.size > sizeof(txctl_log.priv.txdesc_ctl)) + ? sizeof(txctl_log.priv.txdesc_ctl) : + pl_hdr.size; adf_os_mem_copy((void *)&txctl_log.priv.txdesc_ctl, ((void *)data + sizeof(struct ath_pktlog_hdr)), pl_hdr.size); diff --git a/drivers/thermal/msm_thermal-dev.c b/drivers/thermal/msm_thermal-dev.c index eb0490a9cad81..ead9765666c84 100644 --- a/drivers/thermal/msm_thermal-dev.c +++ b/drivers/thermal/msm_thermal-dev.c @@ -268,6 +268,7 @@ static long msm_thermal_process_voltage_table_req( voltage->voltage_table[idx] = voltage_table_ptr[cluster_id][table_idx]; } + voltage->voltage_table_len = idx; copy_and_return: ret = copy_to_user((void __user *)(*arg), query, diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 802df033e24cd..62e3b48062e3b 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1028,6 +1028,11 @@ static int acm_probe(struct usb_interface *intf, } while (buflen > 0) { + if ((buflen < buffer[0]) || (buffer[0] < 3)) { + dev_err(&intf->dev, "invalid descriptor buffer length\n"); + break; + } + if (buffer[1] != USB_DT_CS_INTERFACE) { dev_err(&intf->dev, "skipping garbage\n"); goto next_desc; diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 07133d0c971b4..e5b24e51946d2 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -841,7 +841,12 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) if (!buffer) goto err; - while (buflen > 2) { + while (buflen > 0) { + if ((buflen < buffer[0]) || (buffer[0] < 3)) { + dev_err(&intf->dev, "invalid descriptor buffer length\n"); + goto err; + } + if (buffer[1] != USB_DT_CS_INTERFACE) { dev_err(&intf->dev, "skipping garbage\n"); goto next_desc; diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index a4fa0a35d24b4..e6e5cb83b8f92 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -500,6 +500,9 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx, unsigned iad_num = 0; memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE); + nintf = nintf_orig = config->desc.bNumInterfaces; + config->desc.bNumInterfaces = 0; // Adjusted later + if (config->desc.bDescriptorType != USB_DT_CONFIG || config->desc.bLength < USB_DT_CONFIG_SIZE || config->desc.bLength > size) { @@ -513,7 +516,6 @@ static int usb_parse_configuration(struct usb_device *dev, int cfgidx, buffer += config->desc.bLength; size -= config->desc.bLength; - nintf = nintf_orig = config->desc.bNumInterfaces; if (nintf > USB_MAXINTERFACES) { dev_warn(ddev, "config %d has too many interfaces: %d, " "using maximum allowed: %d\n", diff --git a/drivers/usb/dwc3/dbm-1_4.c b/drivers/usb/dwc3/dbm-1_4.c index 661a938e055c5..1caf7983f60f4 100644 --- a/drivers/usb/dwc3/dbm-1_4.c +++ b/drivers/usb/dwc3/dbm-1_4.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2014, 2018 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -372,7 +372,6 @@ static int msm_dbm_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct dbm *dbm; struct resource *res; - int ret = 0; dbm_data = devm_kzalloc(dev, sizeof(*dbm_data), GFP_KERNEL); if (!dbm_data) @@ -382,24 +381,21 @@ static int msm_dbm_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "missing memory base resource\n"); - ret = -ENODEV; - goto free_dbm_data; + return -ENODEV; } dbm_data->base = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res)); if (!dbm_data->base) { dev_err(&pdev->dev, "ioremap failed\n"); - ret = -ENOMEM; - goto free_dbm_data; + return -ENOMEM; } dbm = devm_kzalloc(dev, sizeof(*dbm), GFP_KERNEL); if (!dbm) { dev_err(&pdev->dev, "not enough memory\n"); - ret = -ENOMEM; - goto free_dbm_data; + return -ENOMEM; } dbm->dev = dev; @@ -418,20 +414,6 @@ static int msm_dbm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dbm); return usb_add_dbm(dbm); - -free_dbm_data: - kfree(dbm_data); - return ret; -} - -static int msm_dbm_remove(struct platform_device *pdev) -{ - struct dbm *dbm = platform_get_drvdata(pdev); - - kfree(dbm); - kfree(dbm_data); - - return 0; } static const struct of_device_id msm_dbm_1_4_id_table[] = { @@ -444,7 +426,6 @@ MODULE_DEVICE_TABLE(of, msm_dbm_1_4_id_table); static struct platform_driver msm_dbm_driver = { .probe = msm_dbm_probe, - .remove = msm_dbm_remove, .driver = { .name = "msm-usb-dbm-1-4", .of_match_table = of_match_ptr(msm_dbm_1_4_id_table), diff --git a/drivers/usb/dwc3/dbm-1_5.c b/drivers/usb/dwc3/dbm-1_5.c index 2a3609ef6ffb6..7f6c5f0bfe15d 100644 --- a/drivers/usb/dwc3/dbm-1_5.c +++ b/drivers/usb/dwc3/dbm-1_5.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2015, 2018 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -387,7 +387,6 @@ static int msm_dbm_probe(struct platform_device *pdev) struct device_node *node = pdev->dev.of_node; struct dbm *dbm; struct resource *res; - int ret = 0; dbm_data = devm_kzalloc(dev, sizeof(*dbm_data), GFP_KERNEL); if (!dbm_data) @@ -397,24 +396,21 @@ static int msm_dbm_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "missing memory base resource\n"); - ret = -ENODEV; - goto free_dbm_data; + return -ENODEV; } dbm_data->base = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res)); if (!dbm_data->base) { dev_err(&pdev->dev, "ioremap failed\n"); - ret = -ENOMEM; - goto free_dbm_data; + return -ENOMEM; } dbm = devm_kzalloc(dev, sizeof(*dbm), GFP_KERNEL); if (!dbm) { dev_err(&pdev->dev, "not enough memory\n"); - ret = -ENOMEM; - goto free_dbm_data; + return -ENOMEM; } dbm_data->dbm_reset_ep_after_lpm = of_property_read_bool(node, @@ -437,20 +433,6 @@ static int msm_dbm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dbm); return usb_add_dbm(dbm); - -free_dbm_data: - kfree(dbm_data); - return ret; -} - -static int msm_dbm_remove(struct platform_device *pdev) -{ - struct dbm *dbm = platform_get_drvdata(pdev); - - kfree(dbm); - kfree(dbm_data); - - return 0; } static const struct of_device_id msm_dbm_1_5_id_table[] = { @@ -463,7 +445,6 @@ MODULE_DEVICE_TABLE(of, msm_dbm_1_5_id_table); static struct platform_driver msm_dbm_driver = { .probe = msm_dbm_probe, - .remove = msm_dbm_remove, .driver = { .name = "msm-usb-dbm-1-5", .of_match_table = of_match_ptr(msm_dbm_1_5_id_table), diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 04851ab4796a4..f0df6eee63ca0 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -653,7 +653,7 @@ static ssize_t dwc3_store_ep_num(struct file *file, const char __user *ubuf, unsigned int num, dir, temp; unsigned long flags; - if (copy_from_user(kbuf, ubuf, count > 10 ? 10 : count)) + if (copy_from_user(kbuf, ubuf, min_t(size_t, sizeof(kbuf) - 1, count))) return -EFAULT; if (sscanf(kbuf, "%u %u", &num, &dir) != 2) diff --git a/drivers/video/msm/mdss/Kconfig b/drivers/video/msm/mdss/Kconfig index c9eae28b745d3..3029d0ef7498b 100644 --- a/drivers/video/msm/mdss/Kconfig +++ b/drivers/video/msm/mdss/Kconfig @@ -62,11 +62,3 @@ config FB_MSM_MDSS_XLOG_DEBUG features to: Dump MDSS registers during driver errors, panic driver during fatal errors and enable some display-driver logging into an internal buffer (this avoids logging overhead). - -config FB_MSM_MDSS_KCAL_CTRL - depends on FB_MSM_MDSS - bool "MDSS color control" - default y - ---help--- - Enable sysfs for post-processing control of mdss-mdp5 display - controllers in MDSS. diff --git a/drivers/video/msm/mdss/Makefile b/drivers/video/msm/mdss/Makefile index f0a76d813955b..c90b04ec0c75c 100644 --- a/drivers/video/msm/mdss/Makefile +++ b/drivers/video/msm/mdss/Makefile @@ -54,5 +54,3 @@ obj-$(CONFIG_COMPAT) += mdss_compat_utils.o obj-$(CONFIG_FB_MSM_MDSS) += mdss_livedisplay.o obj-$(CONFIG_FB_MSM_MDSS) += lcd_notify.o - -obj-$(CONFIG_FB_MSM_MDSS_KCAL_CTRL) += mdss_mdp_kcal_ctrl.o diff --git a/drivers/video/msm/mdss/mdss_dsi_panel.c b/drivers/video/msm/mdss/mdss_dsi_panel.c index 69d1644877f2e..bcb44e5b00f2a 100755 --- a/drivers/video/msm/mdss/mdss_dsi_panel.c +++ b/drivers/video/msm/mdss/mdss_dsi_panel.c @@ -2048,19 +2048,24 @@ static int mdss_dsi_panel_parse_display_timings(struct device_node *np, timings_np = of_get_child_by_name(np, "qcom,mdss-dsi-display-timings"); if (!timings_np) { - struct dsi_panel_timing pt; - memset(&pt, 0, sizeof(struct dsi_panel_timing)); + struct dsi_panel_timing *pt; + + pt = kzalloc(sizeof(*pt), GFP_KERNEL); + if (!pt) + return -ENOMEM; /* * display timings node is not available, fallback to reading * timings directly from root node instead */ pr_debug("reading display-timings from panel node\n"); - rc = mdss_dsi_panel_timing_from_dt(np, &pt); + rc = mdss_dsi_panel_timing_from_dt(np, pt); if (!rc) { mdss_dsi_panel_config_res_properties(np, - panel_data->panel_info.sim_panel_mode, &pt); - rc = mdss_dsi_panel_timing_switch(ctrl, &pt.timing); + panel_data->panel_info.sim_panel_mode, pt); + rc = mdss_dsi_panel_timing_switch(ctrl, &pt->timing); + } else { + kfree(pt); } return rc; } diff --git a/drivers/video/msm/mdss/mdss_fb.c b/drivers/video/msm/mdss/mdss_fb.c index c223797e05fe3..145705e0250ba 100755 --- a/drivers/video/msm/mdss/mdss_fb.c +++ b/drivers/video/msm/mdss/mdss_fb.c @@ -72,12 +72,6 @@ #define BLANK_FLAG_LP FB_BLANK_VSYNC_SUSPEND #define BLANK_FLAG_ULP FB_BLANK_NORMAL -#define MDSS_BRIGHT_TO_BL_DIM(out, v) do {\ - out = (12*v*v+1393*v+3060)/4465;\ - } while (0) -bool backlight_dimmer = false; -module_param(backlight_dimmer, bool, 0755); - static struct fb_info *fbi_list[MAX_FBI_LIST]; static int fbi_list_index; @@ -276,15 +270,10 @@ static void mdss_fb_set_bl_brightness(struct led_classdev *led_cdev, if(!value) count = 1; #endif - - if (backlight_dimmer) { - MDSS_BRIGHT_TO_BL_DIM(bl_lvl, value); - } else { - /* This maps android backlight level 0 to 255 into - driver backlight level 0 to bl_max with rounding */ - MDSS_BRIGHT_TO_BL(bl_lvl, value, mfd->panel_info->bl_max, - mfd->panel_info->brightness_max); - } + /* This maps android backlight level 0 to 255 into + driver backlight level 0 to bl_max with rounding */ + MDSS_BRIGHT_TO_BL(bl_lvl, value, mfd->panel_info->bl_max, + mfd->panel_info->brightness_max); if (!bl_lvl && value) bl_lvl = 1; diff --git a/drivers/video/msm/mdss/mdss_livedisplay.c b/drivers/video/msm/mdss/mdss_livedisplay.c index ab63904a18f8b..d34f5ced902d5 100644 --- a/drivers/video/msm/mdss/mdss_livedisplay.c +++ b/drivers/video/msm/mdss/mdss_livedisplay.c @@ -413,8 +413,6 @@ static ssize_t mdss_livedisplay_set_aco(struct device *dev, mdss_livedisplay_update(mlc, MODE_AUTO_CONTRAST); } - mutex_unlock(&mlc->lock); - return count; } diff --git a/drivers/video/msm/mdss/mdss_mdp_kcal_ctrl.c b/drivers/video/msm/mdss/mdss_mdp_kcal_ctrl.c deleted file mode 100644 index fa38f7627856c..0000000000000 --- a/drivers/video/msm/mdss/mdss_mdp_kcal_ctrl.c +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. - * Copyright (c) 2013, LGE Inc. All rights reserved - * Copyright (c) 2014 savoca - * Copyright (c) 2014 Paul Reioux - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include - -#include "mdss_mdp.h" - -#define DEF_PCC 0x100 -#define DEF_PA 0xff -#define PCC_ADJ 0x80 - -struct kcal_lut_data { - int red; - int green; - int blue; - int minimum; - int enable; - int invert; - int sat; - int hue; - int val; - int cont; -}; - -static int mdss_mdp_kcal_display_commit(void) -{ - int i; - int ret = 0; - struct mdss_mdp_ctl *ctl; - struct mdss_data_type *mdata = mdss_mdp_get_mdata(); - - for (i = 0; i < mdata->nctl; i++) { - ctl = mdata->ctl_off + i; - /* pp setup requires mfd */ - if (mdss_mdp_ctl_is_power_on(ctl) && ctl->mfd && - ctl->mfd->index == 0) { - ret = mdss_mdp_pp_setup(ctl); - if (ret) - pr_err("%s: setup failed: %d\n", __func__, ret); - } - } - - return ret; -} - -static void mdss_mdp_kcal_update_pcc(struct kcal_lut_data *lut_data) -{ - u32 copyback = 0; - struct mdp_pcc_cfg_data pcc_config; - - memset(&pcc_config, 0, sizeof(struct mdp_pcc_cfg_data)); - - lut_data->red = lut_data->red < lut_data->minimum ? - lut_data->minimum : lut_data->red; - lut_data->green = lut_data->green < lut_data->minimum ? - lut_data->minimum : lut_data->green; - lut_data->blue = lut_data->blue < lut_data->minimum ? - lut_data->minimum : lut_data->blue; - - pcc_config.block = MDP_LOGICAL_BLOCK_DISP_0; - pcc_config.ops = lut_data->enable ? - MDP_PP_OPS_WRITE | MDP_PP_OPS_ENABLE : - MDP_PP_OPS_WRITE | MDP_PP_OPS_DISABLE; - pcc_config.r.r = lut_data->red * PCC_ADJ; - pcc_config.g.g = lut_data->green * PCC_ADJ; - pcc_config.b.b = lut_data->blue * PCC_ADJ; - - if (lut_data->invert) { - pcc_config.r.c = pcc_config.g.c = - pcc_config.b.c = 0x7ff8; - pcc_config.r.r |= (0xffff << 16); - pcc_config.g.g |= (0xffff << 16); - pcc_config.b.b |= (0xffff << 16); - } - - mdss_mdp_pcc_config(&pcc_config, ©back); -} - -static void mdss_mdp_kcal_read_pcc(struct kcal_lut_data *lut_data) -{ - u32 copyback = 0; - struct mdp_pcc_cfg_data pcc_config; - - memset(&pcc_config, 0, sizeof(struct mdp_pcc_cfg_data)); - - pcc_config.block = MDP_LOGICAL_BLOCK_DISP_0; - pcc_config.ops = MDP_PP_OPS_READ; - - mdss_mdp_pcc_config(&pcc_config, ©back); - - /* LiveDisplay disables pcc when using default values and regs - * are zeroed on pp resume, so throw these values out. - */ - if (!pcc_config.r.r && !pcc_config.g.g && !pcc_config.b.b) - return; - - lut_data->red = (pcc_config.r.r & 0xffff) / PCC_ADJ; - lut_data->green = (pcc_config.g.g & 0xffff) / PCC_ADJ; - lut_data->blue = (pcc_config.b.b & 0xffff) / PCC_ADJ; -} - -static void mdss_mdp_kcal_update_pa(struct kcal_lut_data *lut_data) -{ - u32 copyback = 0; - struct mdp_pa_cfg_data pa_config; - struct mdp_pa_v2_cfg_data pa_v2_config; - struct mdss_data_type *mdata = mdss_mdp_get_mdata(); - - if (mdata->mdp_rev < MDSS_MDP_HW_REV_103) { - memset(&pa_config, 0, sizeof(struct mdp_pa_cfg_data)); - - pa_config.block = MDP_LOGICAL_BLOCK_DISP_0; - pa_config.pa_data.flags = lut_data->enable ? - MDP_PP_OPS_WRITE | MDP_PP_OPS_ENABLE : - MDP_PP_OPS_WRITE | MDP_PP_OPS_DISABLE; - pa_config.pa_data.hue_adj = lut_data->hue; - pa_config.pa_data.sat_adj = lut_data->sat; - pa_config.pa_data.val_adj = lut_data->val; - pa_config.pa_data.cont_adj = lut_data->cont; - - mdss_mdp_pa_config(&pa_config, ©back); - } else { - memset(&pa_v2_config, 0, sizeof(struct mdp_pa_v2_cfg_data)); - - pa_v2_config.block = MDP_LOGICAL_BLOCK_DISP_0; - pa_v2_config.pa_v2_data.flags = lut_data->enable ? - MDP_PP_OPS_WRITE | MDP_PP_OPS_ENABLE : - MDP_PP_OPS_WRITE | MDP_PP_OPS_DISABLE; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_HUE_ENABLE; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_HUE_MASK; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_SAT_ENABLE; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_SAT_MASK; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_VAL_ENABLE; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_VAL_MASK; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_CONT_ENABLE; - pa_v2_config.pa_v2_data.flags |= MDP_PP_PA_CONT_MASK; - pa_v2_config.pa_v2_data.global_hue_adj = lut_data->hue; - pa_v2_config.pa_v2_data.global_sat_adj = lut_data->sat; - pa_v2_config.pa_v2_data.global_val_adj = lut_data->val; - pa_v2_config.pa_v2_data.global_cont_adj = lut_data->cont; - - mdss_mdp_pa_v2_config(&pa_v2_config, ©back); - } -} - -static ssize_t kcal_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - int kcal_r, kcal_g, kcal_b, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = sscanf(buf, "%d %d %d", &kcal_r, &kcal_g, &kcal_b); - if ((r != 3) || (kcal_r < 1 || kcal_r > 256) || - (kcal_g < 1 || kcal_g > 256) || (kcal_b < 1 || kcal_b > 256)) - return -EINVAL; - - lut_data->red = kcal_r; - lut_data->green = kcal_g; - lut_data->blue = kcal_b; - - mdss_mdp_kcal_update_pcc(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - mdss_mdp_kcal_read_pcc(lut_data); - - return scnprintf(buf, PAGE_SIZE, "%d %d %d\n", - lut_data->red, lut_data->green, lut_data->blue); -} - -static ssize_t kcal_min_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int kcal_min, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = kstrtoint(buf, 10, &kcal_min); - if ((r) || (kcal_min < 1 || kcal_min > 256)) - return -EINVAL; - - lut_data->minimum = kcal_min; - - mdss_mdp_kcal_update_pcc(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_min_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - return scnprintf(buf, PAGE_SIZE, "%d\n", lut_data->minimum); -} - -static ssize_t kcal_enable_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int kcal_enable, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = kstrtoint(buf, 10, &kcal_enable); - if ((r) || (kcal_enable != 0 && kcal_enable != 1) || - (lut_data->enable == kcal_enable)) - return -EINVAL; - - lut_data->enable = kcal_enable; - - mdss_mdp_kcal_update_pcc(lut_data); - mdss_mdp_kcal_update_pa(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_enable_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - return scnprintf(buf, PAGE_SIZE, "%d\n", lut_data->enable); -} - -static ssize_t kcal_invert_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int kcal_invert, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = kstrtoint(buf, 10, &kcal_invert); - if ((r) || (kcal_invert != 0 && kcal_invert != 1) || - (lut_data->invert == kcal_invert)) - return -EINVAL; - - lut_data->invert = kcal_invert; - - mdss_mdp_kcal_update_pcc(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_invert_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - return scnprintf(buf, PAGE_SIZE, "%d\n", lut_data->invert); -} - -static ssize_t kcal_sat_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int kcal_sat, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = kstrtoint(buf, 10, &kcal_sat); - if ((r) || ((kcal_sat < 224 || kcal_sat > 383) && kcal_sat != 128)) - return -EINVAL; - - lut_data->sat = kcal_sat; - - mdss_mdp_kcal_update_pa(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_sat_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - return scnprintf(buf, PAGE_SIZE, "%d\n", lut_data->sat); -} - -static ssize_t kcal_hue_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int kcal_hue, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = kstrtoint(buf, 10, &kcal_hue); - if ((r) || (kcal_hue < 0 || kcal_hue > 1536)) - return -EINVAL; - - lut_data->hue = kcal_hue; - - mdss_mdp_kcal_update_pa(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_hue_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - return scnprintf(buf, PAGE_SIZE, "%d\n", lut_data->hue); -} - -static ssize_t kcal_val_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int kcal_val, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = kstrtoint(buf, 10, &kcal_val); - if ((r) || (kcal_val < 128 || kcal_val > 383)) - return -EINVAL; - - lut_data->val = kcal_val; - - mdss_mdp_kcal_update_pa(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_val_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - return scnprintf(buf, PAGE_SIZE, "%d\n", lut_data->val); -} - -static ssize_t kcal_cont_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int kcal_cont, r; - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - r = kstrtoint(buf, 10, &kcal_cont); - if ((r) || (kcal_cont < 128 || kcal_cont > 383)) - return -EINVAL; - - lut_data->cont = kcal_cont; - - mdss_mdp_kcal_update_pa(lut_data); - mdss_mdp_kcal_display_commit(); - - return count; -} - -static ssize_t kcal_cont_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kcal_lut_data *lut_data = dev_get_drvdata(dev); - - return scnprintf(buf, PAGE_SIZE, "%d\n", lut_data->cont); -} - -static DEVICE_ATTR(kcal, S_IWUSR | S_IRUGO, kcal_show, kcal_store); -static DEVICE_ATTR(kcal_min, S_IWUSR | S_IRUGO, kcal_min_show, kcal_min_store); -static DEVICE_ATTR(kcal_enable, S_IWUSR | S_IRUGO, kcal_enable_show, - kcal_enable_store); -static DEVICE_ATTR(kcal_invert, S_IWUSR | S_IRUGO, kcal_invert_show, - kcal_invert_store); -static DEVICE_ATTR(kcal_sat, S_IWUSR | S_IRUGO, kcal_sat_show, kcal_sat_store); -static DEVICE_ATTR(kcal_hue, S_IWUSR | S_IRUGO, kcal_hue_show, kcal_hue_store); -static DEVICE_ATTR(kcal_val, S_IWUSR | S_IRUGO, kcal_val_show, kcal_val_store); -static DEVICE_ATTR(kcal_cont, S_IWUSR | S_IRUGO, kcal_cont_show, - kcal_cont_store); - -static int kcal_ctrl_probe(struct platform_device *pdev) -{ - int ret; - struct kcal_lut_data *lut_data; - - lut_data = devm_kzalloc(&pdev->dev, sizeof(*lut_data), GFP_KERNEL); - if (!lut_data) { - pr_err("%s: failed to allocate memory for lut_data\n", - __func__); - return -ENOMEM; - } - - platform_set_drvdata(pdev, lut_data); - - lut_data->enable = 0x1; - lut_data->red = DEF_PCC; - lut_data->green = DEF_PCC; - lut_data->blue = DEF_PCC; - lut_data->minimum = 0x23; - lut_data->invert = 0x0; - lut_data->hue = 0x0; - lut_data->sat = DEF_PA; - lut_data->val = DEF_PA; - lut_data->cont = DEF_PA; - - mdss_mdp_kcal_update_pcc(lut_data); - mdss_mdp_kcal_update_pa(lut_data); - mdss_mdp_kcal_display_commit(); - - ret = device_create_file(&pdev->dev, &dev_attr_kcal); - ret |= device_create_file(&pdev->dev, &dev_attr_kcal_min); - ret |= device_create_file(&pdev->dev, &dev_attr_kcal_enable); - ret |= device_create_file(&pdev->dev, &dev_attr_kcal_invert); - ret |= device_create_file(&pdev->dev, &dev_attr_kcal_sat); - ret |= device_create_file(&pdev->dev, &dev_attr_kcal_hue); - ret |= device_create_file(&pdev->dev, &dev_attr_kcal_val); - ret |= device_create_file(&pdev->dev, &dev_attr_kcal_cont); - if (ret) { - pr_err("%s: unable to create sysfs entries\n", __func__); - return ret; - } - - return 0; -} - -static int kcal_ctrl_remove(struct platform_device *pdev) -{ - device_remove_file(&pdev->dev, &dev_attr_kcal); - device_remove_file(&pdev->dev, &dev_attr_kcal_min); - device_remove_file(&pdev->dev, &dev_attr_kcal_enable); - device_remove_file(&pdev->dev, &dev_attr_kcal_invert); - device_remove_file(&pdev->dev, &dev_attr_kcal_sat); - device_remove_file(&pdev->dev, &dev_attr_kcal_hue); - device_remove_file(&pdev->dev, &dev_attr_kcal_val); - device_remove_file(&pdev->dev, &dev_attr_kcal_cont); - - return 0; -} - -static struct platform_driver kcal_ctrl_driver = { - .probe = kcal_ctrl_probe, - .remove = kcal_ctrl_remove, - .driver = { - .name = "kcal_ctrl", - }, -}; - -static struct platform_device kcal_ctrl_device = { - .name = "kcal_ctrl", -}; - -static int __init kcal_ctrl_init(void) -{ - if (platform_driver_register(&kcal_ctrl_driver)) - return -ENODEV; - - if (platform_device_register(&kcal_ctrl_device)) - return -ENODEV; - - pr_info("%s: registered\n", __func__); - - return 0; -} - -static void __exit kcal_ctrl_exit(void) -{ - platform_device_unregister(&kcal_ctrl_device); - platform_driver_unregister(&kcal_ctrl_driver); -} - -module_init(kcal_ctrl_init); -module_exit(kcal_ctrl_exit); diff --git a/drivers/video/msm/mdss/mdss_mdp_rotator.c b/drivers/video/msm/mdss/mdss_mdp_rotator.c index 1019016a58288..136375cf0cf64 100644 --- a/drivers/video/msm/mdss/mdss_mdp_rotator.c +++ b/drivers/video/msm/mdss/mdss_mdp_rotator.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2016, 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -43,6 +43,7 @@ struct mdss_mdp_rot_pipe { struct mdss_mdp_rot_session_mgr { struct list_head queue; struct mutex session_lock; + struct mutex req_lock; int session_id; int session_count; @@ -77,6 +78,7 @@ int mdss_mdp_rot_mgr_init(void) mutex_init(&rot_mgr->session_lock); mutex_init(&rot_mgr->pipe_lock); + mutex_init(&rot_mgr->req_lock); INIT_LIST_HEAD(&rot_mgr->queue); rot_mgr->rot_work_queue = create_workqueue("rot_commit_workq"); if (!rot_mgr->rot_work_queue) { @@ -1017,9 +1019,11 @@ int mdss_mdp_rotator_play(struct msm_fb_data_type *mfd, u32 flgs; struct mdss_mdp_data src_buf; + mutex_lock(&rot_mgr->req_lock); rot = mdss_mdp_rot_mgr_get_session(req->id); if (!rot) { pr_err("invalid session id=%x\n", req->id); + mutex_unlock(&rot_mgr->req_lock); return -ENOENT; } @@ -1070,6 +1074,7 @@ int mdss_mdp_rotator_play(struct msm_fb_data_type *mfd, dst_buf_fail: mutex_unlock(&rot->lock); + mutex_unlock(&rot_mgr->req_lock); mdss_iommu_ctrl(0); return ret; } @@ -1079,9 +1084,11 @@ int mdss_mdp_rotator_unset(int ndx) struct mdss_mdp_rotator_session *rot; int ret = 0; + mutex_lock(&rot_mgr->req_lock); rot = mdss_mdp_rot_mgr_get_session(ndx); if (rot) ret = mdss_mdp_rotator_release(rot); + mutex_unlock(&rot_mgr->req_lock); return ret; } diff --git a/drivers/video/msm/mdss/mdss_mdp_util.c b/drivers/video/msm/mdss/mdss_mdp_util.c index d8878ff6a2ae0..b17c66ef69773 100644 --- a/drivers/video/msm/mdss/mdss_mdp_util.c +++ b/drivers/video/msm/mdss/mdss_mdp_util.c @@ -438,6 +438,8 @@ int mdss_mdp_get_plane_sizes(u32 format, u32 w, u32 h, if (ps == NULL) return -EINVAL; + memset(ps, 0, sizeof(struct mdss_mdp_plane_sizes)); + if ((w > MAX_IMG_WIDTH) || (h > MAX_IMG_HEIGHT)) return -ERANGE; @@ -446,7 +448,6 @@ int mdss_mdp_get_plane_sizes(u32 format, u32 w, u32 h, return -EINVAL; bpp = fmt->bpp; - memset(ps, 0, sizeof(struct mdss_mdp_plane_sizes)); if (bwc_mode) { u32 height, meta_size; diff --git a/fs/Kconfig b/fs/Kconfig index 2618766145c45..a8a88c1d07055 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -96,6 +96,7 @@ menu "DOS/FAT/NT Filesystems" source "fs/fat/Kconfig" source "fs/ntfs/Kconfig" +source "fs/sdfat/Kconfig" #ifdef VENDOR_EDIT source "fs/exfat/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index ce574e3f2dd91..01774bdc8eaf6 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -78,6 +78,7 @@ obj-$(CONFIG_HUGETLBFS) += hugetlbfs/ obj-$(CONFIG_CODA_FS) += coda/ obj-$(CONFIG_MINIX_FS) += minix/ obj-$(CONFIG_FAT_FS) += fat/ +obj-$(CONFIG_SDFAT_FS) += sdfat/ obj-$(CONFIG_BFS_FS) += bfs/ obj-$(CONFIG_ISO9660_FS) += isofs/ obj-$(CONFIG_HFSPLUS_FS) += hfsplus/ # Before hfs to find wrapped HFS+ diff --git a/fs/buffer.c b/fs/buffer.c index 963b348199007..c36b69324ada5 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1263,7 +1263,7 @@ static struct buffer_head *__bread_slow(struct buffer_head *bh) * a local interrupt disable for that. */ -#define BH_LRU_SIZE 8 +#define BH_LRU_SIZE 16 struct bh_lru { struct buffer_head *bhs[BH_LRU_SIZE]; diff --git a/fs/dcache.c b/fs/dcache.c index dcb875bdd1ab3..aa42c7930c14d 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -211,15 +211,49 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c return dentry_string_cmp(cs, ct, tcount); } +struct external_name { + union { + atomic_t count; + struct rcu_head head; + } u; + unsigned char name[]; +}; + +static inline struct external_name *external_name(struct dentry *dentry) +{ + return container_of(dentry->d_name.name, struct external_name, name[0]); +} + static void __d_free(struct rcu_head *head) { struct dentry *dentry = container_of(head, struct dentry, d_u.d_rcu); - if (dname_external(dentry)) - kfree(dentry->d_name.name); kmem_cache_free(dentry_cache, dentry); } +static void __d_free_external(struct rcu_head *head) +{ + struct dentry *dentry = container_of(head, struct dentry, d_u.d_rcu); + kfree(external_name(dentry)); + kmem_cache_free(dentry_cache, dentry); +} + +static void dentry_free(struct dentry *dentry) +{ + if (unlikely(dname_external(dentry))) { + struct external_name *p = external_name(dentry); + if (likely(atomic_dec_and_test(&p->u.count))) { + call_rcu(&dentry->d_u.d_rcu, __d_free_external); + return; + } + } + /* if dentry was never visible to RCU, immediate free is OK */ + if (!(dentry->d_flags & DCACHE_RCUACCESS)) + __d_free(&dentry->d_u.d_rcu); + else + call_rcu(&dentry->d_u.d_rcu, __d_free); +} + /* * no locks, please. */ @@ -231,11 +265,7 @@ static void d_free(struct dentry *dentry) if (dentry->d_op && dentry->d_op->d_release) dentry->d_op->d_release(dentry); - /* if dentry was never visible to RCU, immediate free is OK */ - if (!(dentry->d_flags & DCACHE_RCUACCESS)) - __d_free(&dentry->d_u.d_rcu); - else - call_rcu(&dentry->d_u.d_rcu, __d_free); + dentry_free(dentry); } void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry) @@ -1295,11 +1325,14 @@ struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) */ dentry->d_iname[DNAME_INLINE_LEN-1] = 0; if (name->len > DNAME_INLINE_LEN-1) { - dname = kmalloc(name->len + 1, GFP_KERNEL); - if (!dname) { + size_t size = offsetof(struct external_name, name[1]); + struct external_name *p = kmalloc(size + name->len, GFP_KERNEL); + if (!p) { kmem_cache_free(dentry_cache, dentry); return NULL; } + atomic_set(&p->u.count, 1); + dname = p->name; } else { dname = dentry->d_iname; } @@ -2191,8 +2224,8 @@ EXPORT_SYMBOL(dentry_update_name_case); static void switch_names(struct dentry *dentry, struct dentry *target) { - if (dname_external(target)) { - if (dname_external(dentry)) { + if (unlikely(dname_external(target))) { + if (unlikely(dname_external(dentry))) { /* * Both external: swap the pointers */ @@ -2208,7 +2241,7 @@ static void switch_names(struct dentry *dentry, struct dentry *target) target->d_name.name = target->d_iname; } } else { - if (dname_external(dentry)) { + if (unlikely(dname_external(dentry))) { /* * dentry:external, target:internal. Give dentry's * storage to target and make dentry internal @@ -2230,6 +2263,25 @@ static void switch_names(struct dentry *dentry, struct dentry *target) swap(dentry->d_name.len, target->d_name.len); } +static void copy_name(struct dentry *dentry, struct dentry *target) +{ + struct external_name *old_name = NULL; + if (unlikely(dname_external(dentry))) + old_name = external_name(dentry); + if (unlikely(dname_external(target))) { + atomic_inc(&external_name(target)->u.count); + dentry->d_name = target->d_name; + } else { + memcpy(dentry->d_iname, target->d_name.name, + target->d_name.len + 1); + dentry->d_name.name = dentry->d_iname; + dentry->d_name.len = target->d_name.len; + } + if (old_name && likely(atomic_dec_and_test(&old_name->u.count))) + kfree_rcu(old_name, u.head); +} + + static void dentry_lock_for_move(struct dentry *dentry, struct dentry *target) { /* @@ -2316,7 +2368,7 @@ static void __d_move(struct dentry * dentry, struct dentry * target) list_del(&target->d_child); /* Switch the names.. */ - switch_names(dentry, target); + copy_name(dentry, target); swap(dentry->d_name.hash, target->d_name.hash); /* ... and switch the parents */ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 92d569a638667..0f6b1fca96ca7 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1877,8 +1877,10 @@ static ssize_t fuse_dev_do_write(struct fuse_conn *fc, err = copy_out_args(cs, &req->out, nbytes); if (req->in.h.opcode == FUSE_CANONICAL_PATH) { - req->out.h.error = kern_path((char *)req->out.args[0].value, 0, - req->canonical_path); + char *path = (char *)req->out.args[0].value; + + path[req->out.args[0].size - 1] = 0; + req->out.h.error = kern_path(path, 0, req->canonical_path); } fuse_copy_finish(cs); diff --git a/fs/sdcardfs/dentry.c b/fs/sdcardfs/dentry.c index c8f3bcbfb183b..e33707897cbcc 100755 --- a/fs/sdcardfs/dentry.c +++ b/fs/sdcardfs/dentry.c @@ -51,7 +51,6 @@ static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) * whether the base obbpath has been changed or not */ if (is_obbpath_invalid(dentry)) { - d_drop(dentry); return 0; } @@ -65,7 +64,6 @@ static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) if ((lower_dentry->d_flags & DCACHE_OP_REVALIDATE)) { err = lower_dentry->d_op->d_revalidate(lower_dentry, flags); if (err == 0) { - d_drop(dentry); goto out; } } @@ -73,14 +71,12 @@ static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) spin_lock(&lower_dentry->d_lock); if (d_unhashed(lower_dentry)) { spin_unlock(&lower_dentry->d_lock); - d_drop(dentry); err = 0; goto out; } spin_unlock(&lower_dentry->d_lock); if (parent_lower_dentry != lower_cur_parent_dentry) { - d_drop(dentry); err = 0; goto out; } @@ -94,7 +90,6 @@ static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) } if (!qstr_case_eq(&dentry->d_name, &lower_dentry->d_name)) { - __d_drop(dentry); err = 0; } @@ -113,7 +108,6 @@ static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) if (inode) { data = top_data_get(SDCARDFS_I(inode)); if (!data || data->abandoned) { - d_drop(dentry); err = 0; } if (data) @@ -131,6 +125,8 @@ static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) static void sdcardfs_d_release(struct dentry *dentry) { + if (!dentry || !dentry->d_fsdata) + return; /* release and reset the lower paths */ if (has_graft_path(dentry)) sdcardfs_put_reset_orig_path(dentry); diff --git a/fs/sdcardfs/derived_perm.c b/fs/sdcardfs/derived_perm.c index 75beeff149f0b..46af20db62c81 100755 --- a/fs/sdcardfs/derived_perm.c +++ b/fs/sdcardfs/derived_perm.c @@ -32,23 +32,20 @@ static void inherit_derived_state(struct inode *parent, struct inode *child) ci->data->under_android = pi->data->under_android; ci->data->under_cache = pi->data->under_cache; ci->data->under_obb = pi->data->under_obb; - set_top(ci, pi->top_data); } /* helper function for derived state */ void setup_derived_state(struct inode *inode, perm_t perm, userid_t userid, - uid_t uid, bool under_android, - struct sdcardfs_inode_data *top) + uid_t uid) { struct sdcardfs_inode_info *info = SDCARDFS_I(inode); info->data->perm = perm; info->data->userid = userid; info->data->d_uid = uid; - info->data->under_android = under_android; + info->data->under_android = false; info->data->under_cache = false; info->data->under_obb = false; - set_top(info, top); } /* While renaming, there is a point where we want the path from dentry, @@ -58,8 +55,8 @@ void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, const struct qstr *name) { struct sdcardfs_inode_info *info = SDCARDFS_I(dentry->d_inode); - struct sdcardfs_inode_data *parent_data = - SDCARDFS_I(parent->d_inode)->data; + struct sdcardfs_inode_info *parent_info = SDCARDFS_I(parent->d_inode); + struct sdcardfs_inode_data *parent_data = parent_info->data; appid_t appid; unsigned long user_num; int err; @@ -80,13 +77,15 @@ void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, inherit_derived_state(parent->d_inode, dentry->d_inode); /* Files don't get special labels */ - if (!S_ISDIR(dentry->d_inode->i_mode)) + if (!S_ISDIR(dentry->d_inode->i_mode)) { + set_top(info, parent_info); return; + } /* Derive custom permissions based on parent and current node */ switch (parent_data->perm) { case PERM_INHERIT: case PERM_ANDROID_PACKAGE_CACHE: - /* Already inherited above */ + set_top(info, parent_info); break; case PERM_PRE_ROOT: /* Legacy internal layout places users at top level */ @@ -96,7 +95,6 @@ void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, info->data->userid = 0; else info->data->userid = user_num; - set_top(info, info->data); break; case PERM_ROOT: /* Assume masked off by default. */ @@ -104,24 +102,24 @@ void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, /* App-specific directories inside; let anyone traverse */ info->data->perm = PERM_ANDROID; info->data->under_android = true; - set_top(info, info->data); + } else { + set_top(info, parent_info); } break; case PERM_ANDROID: if (qstr_case_eq(name, &q_data)) { /* App-specific directories inside; let anyone traverse */ info->data->perm = PERM_ANDROID_DATA; - set_top(info, info->data); } else if (qstr_case_eq(name, &q_obb)) { /* App-specific directories inside; let anyone traverse */ info->data->perm = PERM_ANDROID_OBB; info->data->under_obb = true; - set_top(info, info->data); /* Single OBB directory is always shared */ } else if (qstr_case_eq(name, &q_media)) { /* App-specific directories inside; let anyone traverse */ info->data->perm = PERM_ANDROID_MEDIA; - set_top(info, info->data); + } else { + set_top(info, parent_info); } break; case PERM_ANDROID_OBB: @@ -132,13 +130,13 @@ void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, if (appid != 0 && !is_excluded(name->name, parent_data->userid)) info->data->d_uid = multiuser_get_uid(parent_data->userid, appid); - set_top(info, info->data); break; case PERM_ANDROID_PACKAGE: if (qstr_case_eq(name, &q_cache)) { info->data->perm = PERM_ANDROID_PACKAGE_CACHE; info->data->under_cache = true; } + set_top(info, parent_info); break; } } diff --git a/fs/sdcardfs/file.c b/fs/sdcardfs/file.c index 1665200f3156e..72338cc3d322c 100755 --- a/fs/sdcardfs/file.c +++ b/fs/sdcardfs/file.c @@ -18,7 +18,6 @@ * General Public License. */ -#include #include "sdcardfs.h" #ifdef CONFIG_SDCARD_FS_FADV_NOACTIVE #include @@ -63,6 +62,7 @@ static ssize_t sdcardfs_write(struct file *file, const char __user *buf, int err = 0; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; + struct inode *inode = dentry->d_inode; /* check disk space */ if (!check_min_free_space(dentry, count, 0)) { @@ -74,10 +74,12 @@ static ssize_t sdcardfs_write(struct file *file, const char __user *buf, err = vfs_write(lower_file, buf, count, ppos); /* update our inode times+sizes upon a successful lower write */ if (err >= 0) { - fsstack_copy_inode_size(dentry->d_inode, - lower_file->f_path.dentry->d_inode); - fsstack_copy_attr_times(dentry->d_inode, - lower_file->f_path.dentry->d_inode); + if (sizeof(loff_t) > sizeof(long)) + mutex_lock(&inode->i_mutex); + fsstack_copy_inode_size(inode, file_inode(lower_file)); + fsstack_copy_attr_times(inode, file_inode(lower_file)); + if (sizeof(loff_t) > sizeof(long)) + mutex_unlock(&inode->i_mutex); } return err; @@ -261,7 +263,6 @@ static int sdcardfs_open(struct inode *inode, struct file *file) fput(lower_file); /* fput calls dput for lower_dentry */ } } else { - fsnotify_open(lower_file); sdcardfs_set_lower_file(file, lower_file); } diff --git a/fs/sdcardfs/inode.c b/fs/sdcardfs/inode.c index 36ae048edd77a..7386e1b49d759 100755 --- a/fs/sdcardfs/inode.c +++ b/fs/sdcardfs/inode.c @@ -270,6 +270,7 @@ static int sdcardfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode struct dentry *lower_dentry; struct vfsmount *lower_mnt; struct dentry *lower_parent_dentry = NULL; + struct dentry *parent_dentry = NULL; struct path lower_path; struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); const struct cred *saved_cred = NULL; @@ -289,11 +290,14 @@ static int sdcardfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir)); /* check disk space */ - if (!check_min_free_space(dentry, 0, 1)) { + parent_dentry = dget_parent(dentry); + if (!check_min_free_space(parent_dentry, 0, 1)) { pr_err("sdcardfs: No minimum free space.\n"); err = -ENOSPC; + dput(parent_dentry); goto out_revert; } + dput(parent_dentry); /* the lower_dentry is negative here */ sdcardfs_get_lower_path(dentry, &lower_path); @@ -828,8 +832,8 @@ static int sdcardfs_setattr(struct vfsmount *mnt, struct dentry *dentry, struct return err; } -static int sdcardfs_fillattr(struct vfsmount *mnt, - struct inode *inode, struct kstat *stat) +static int sdcardfs_fillattr(struct vfsmount *mnt, struct inode *inode, + struct kstat *lower_stat, struct kstat *stat) { struct sdcardfs_inode_info *info = SDCARDFS_I(inode); struct sdcardfs_inode_data *top = top_data_get(info); @@ -845,12 +849,12 @@ static int sdcardfs_fillattr(struct vfsmount *mnt, stat->uid = make_kuid(&init_user_ns, top->d_uid); stat->gid = make_kgid(&init_user_ns, get_gid(mnt, sb, top)); stat->rdev = inode->i_rdev; - stat->size = i_size_read(inode); - stat->atime = inode->i_atime; - stat->mtime = inode->i_mtime; - stat->ctime = inode->i_ctime; - stat->blksize = (1 << inode->i_blkbits); - stat->blocks = inode->i_blocks; + stat->size = lower_stat->size; + stat->atime = lower_stat->atime; + stat->mtime = lower_stat->mtime; + stat->ctime = lower_stat->ctime; + stat->blksize = lower_stat->blksize; + stat->blocks = lower_stat->blocks; data_put(top); return 0; } @@ -876,8 +880,7 @@ static int sdcardfs_getattr(struct vfsmount *mnt, struct dentry *dentry, goto out; sdcardfs_copy_and_fix_attrs(dentry->d_inode, lower_path.dentry->d_inode); - err = sdcardfs_fillattr(mnt, dentry->d_inode, stat); - stat->blocks = lower_stat.blocks; + err = sdcardfs_fillattr(mnt, dentry->d_inode, &lower_stat, stat); out: sdcardfs_put_lower_path(dentry, &lower_path); return err; diff --git a/fs/sdcardfs/lookup.c b/fs/sdcardfs/lookup.c index ace852390d2a3..3395371c72cae 100755 --- a/fs/sdcardfs/lookup.c +++ b/fs/sdcardfs/lookup.c @@ -41,8 +41,6 @@ void sdcardfs_destroy_dentry_cache(void) void free_dentry_private_data(struct dentry *dentry) { - if (!dentry || !dentry->d_fsdata) - return; kmem_cache_free(sdcardfs_dentry_cachep, dentry->d_fsdata); dentry->d_fsdata = NULL; } diff --git a/fs/sdcardfs/main.c b/fs/sdcardfs/main.c index f949fcd0ce928..b60f5edfa834d 100755 --- a/fs/sdcardfs/main.c +++ b/fs/sdcardfs/main.c @@ -323,7 +323,7 @@ static int sdcardfs_read_super(struct vfsmount *mnt, struct super_block *sb, sb->s_root = d_make_root(inode); if (!sb->s_root) { err = -ENOMEM; - goto out_iput; + goto out_sput; } d_set_d_op(sb->s_root, &sdcardfs_ci_dops); @@ -348,13 +348,11 @@ static int sdcardfs_read_super(struct vfsmount *mnt, struct super_block *sb, mutex_lock(&sdcardfs_super_list_lock); if (sb_info->options.multiuser) { setup_derived_state(sb->s_root->d_inode, PERM_PRE_ROOT, - sb_info->options.fs_user_id, AID_ROOT, - false, SDCARDFS_I(sb->s_root->d_inode)->data); + sb_info->options.fs_user_id, AID_ROOT); snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name); } else { setup_derived_state(sb->s_root->d_inode, PERM_ROOT, - sb_info->options.fs_user_id, AID_ROOT, - false, SDCARDFS_I(sb->s_root->d_inode)->data); + sb_info->options.fs_user_id, AID_ROOT); snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name); } fixup_tmp_permissions(sb->s_root->d_inode); @@ -370,8 +368,7 @@ static int sdcardfs_read_super(struct vfsmount *mnt, struct super_block *sb, /* no longer needed: free_dentry_private_data(sb->s_root); */ out_freeroot: dput(sb->s_root); -out_iput: - iput(inode); + sb->s_root = NULL; out_sput: /* drop refs we took earlier */ atomic_dec(&lower_sb->s_active); @@ -431,7 +428,7 @@ void sdcardfs_kill_sb(struct super_block *sb) { struct sdcardfs_sb_info *sbi; - if (sb->s_magic == SDCARDFS_SUPER_MAGIC) { + if (sb->s_magic == SDCARDFS_SUPER_MAGIC && sb->s_fs_info) { sbi = SDCARDFS_SB(sb); mutex_lock(&sdcardfs_super_list_lock); list_del(&sbi->list); diff --git a/fs/sdcardfs/packagelist.c b/fs/sdcardfs/packagelist.c index 886d2f699b804..d9f8ed4961b9f 100755 --- a/fs/sdcardfs/packagelist.c +++ b/fs/sdcardfs/packagelist.c @@ -645,6 +645,7 @@ static struct config_item *extension_details_make_item(struct config_group *grou return ERR_PTR(-ENOMEM); } qstr_init(&extension_details->name, tmp); + extension_details->num = extensions_value->num; ret = insert_ext_gid_entry(&extension_details->name, extensions_value->num); if (ret) { diff --git a/fs/sdcardfs/sdcardfs.h b/fs/sdcardfs/sdcardfs.h index 31c013d28cc45..2c510e4bee57b 100755 --- a/fs/sdcardfs/sdcardfs.h +++ b/fs/sdcardfs/sdcardfs.h @@ -200,6 +200,7 @@ struct sdcardfs_inode_info { struct sdcardfs_inode_data *data; /* top folder for ownership */ + spinlock_t top_lock; struct sdcardfs_inode_data *top_data; struct inode vfs_inode; @@ -379,7 +380,12 @@ static inline struct sdcardfs_inode_data *data_get( static inline struct sdcardfs_inode_data *top_data_get( struct sdcardfs_inode_info *info) { - return data_get(info->top_data); + struct sdcardfs_inode_data *top_data; + + spin_lock(&info->top_lock); + top_data = data_get(info->top_data); + spin_unlock(&info->top_lock); + return top_data; } extern void data_release(struct kref *ref); @@ -401,15 +407,20 @@ static inline void release_own_data(struct sdcardfs_inode_info *info) } static inline void set_top(struct sdcardfs_inode_info *info, - struct sdcardfs_inode_data *top) + struct sdcardfs_inode_info *top_owner) { - struct sdcardfs_inode_data *old_top = info->top_data; + struct sdcardfs_inode_data *old_top; + struct sdcardfs_inode_data *new_top = NULL; + + if (top_owner) + new_top = top_data_get(top_owner); - if (top) - data_get(top); - info->top_data = top; + spin_lock(&info->top_lock); + old_top = info->top_data; + info->top_data = new_top; if (old_top) data_put(old_top); + spin_unlock(&info->top_lock); } static inline int get_gid(struct vfsmount *mnt, @@ -515,8 +526,7 @@ struct limit_search { }; extern void setup_derived_state(struct inode *inode, perm_t perm, - userid_t userid, uid_t uid, bool under_android, - struct sdcardfs_inode_data *top); + userid_t userid, uid_t uid); extern void get_derived_permission(struct dentry *parent, struct dentry *dentry); extern void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, const struct qstr *name); extern void fixup_perms_recursive(struct dentry *dentry, struct limit_search *limit); @@ -659,7 +669,7 @@ static inline bool str_n_case_eq(const char *s1, const char *s2, size_t len) static inline bool qstr_case_eq(const struct qstr *q1, const struct qstr *q2) { - return q1->len == q2->len && str_case_eq(q1->name, q2->name); + return q1->len == q2->len && str_n_case_eq(q1->name, q2->name, q2->len); } #define QSTR_LITERAL(string) QSTR_INIT(string, sizeof(string)-1) diff --git a/fs/sdcardfs/super.c b/fs/sdcardfs/super.c index 87d6f836592e8..cffcdb11cb8ac 100755 --- a/fs/sdcardfs/super.c +++ b/fs/sdcardfs/super.c @@ -215,6 +215,9 @@ static struct inode *sdcardfs_alloc_inode(struct super_block *sb) i->data = d; kref_init(&d->refcount); + i->top_data = d; + spin_lock_init(&i->top_lock); + kref_get(&d->refcount); i->vfs_inode.i_version = 1; return &i->vfs_inode; diff --git a/fs/sdfat/Kconfig b/fs/sdfat/Kconfig new file mode 100644 index 0000000000000..3a15bbb55526f --- /dev/null +++ b/fs/sdfat/Kconfig @@ -0,0 +1,122 @@ +config SDFAT_FS + tristate "sdFAT filesystem support" + default y + select NLS + select NLS_UTF8 + select NLS_CODEPAGE_437 + select NLS_ISO8859_1 + help + If you want to use the sdFAT file system, then you must say Y or M + here to inlucde sdFAT support. + sdFAT is unified FAT-based file system which supports not only fat12/ + 16/32 with vfat but also exfat. sdFAT supports winnt short-name rule. + (winnt: emulate the Windows NT rule for display/create.) + + To compile this as a module, choose M here: the module will be called + sdfat_core and sdfat_fs. + +config SDFAT_USE_FOR_EXFAT + bool "Register sdFAT as exFAT" + default y + depends on SDFAT_FS && !EXFAT_FS + help + If you want to register sdFAT as available for exFAT, say Y. + +config SDFAT_USE_FOR_VFAT + bool "Register sdFAT as VFAT" + default y + depends on SDFAT_FS && !VFAT_FS + help + If you want to register sdFAT as available for VFAT, say Y. + +config SDFAT_DELAYED_META_DIRTY + bool "Enable delayed metadata dirty" + default y + depends on SDFAT_FS + help + If you enable this feature, metadata(FAT/Directory entry) is updated + by flush thread. + +config SDFAT_SUPPORT_DIR_SYNC + bool "Enable supporting dir sync" + default n + depends on SDFAT_FS + help + If you enable this feature, the modification for directory operation + is written to a storage at once. + +config SDFAT_DEFAULT_CODEPAGE + int "Default codepage for sdFAT" + default 437 + depends on SDFAT_FS + help + This option should be set to the codepage of your sdFAT filesystems. + +config SDFAT_DEFAULT_IOCHARSET + string "Default iocharset for sdFAT" + default "utf8" + depends on SDFAT_FS + help + Set this to the default input/output character set you'd + like sdFAT to use. It should probably match the character set + that most of your sdFAT filesystems use, and can be overridden + with the "iocharset" mount option for sdFAT filesystems. + +config SDFAT_CHECK_RO_ATTR + bool "Check read-only attribute" + default n + depends on SDFAT_FS + +config SDFAT_ALIGNED_MPAGE_WRITE + bool "Enable supporting aligned mpage_write" + default y + depends on SDFAT_FS + +config SDFAT_VIRTUAL_XATTR + bool "Virtual xattr support for sdFAT" + default y + depends on SDFAT_FS + help + To support virtual xattr. + +config SDFAT_VIRTUAL_XATTR_SELINUX_LABEL + string "Default string for SELinux label" + default "u:object_r:sdcard_external:s0" + depends on SDFAT_FS && SDFAT_VIRTUAL_XATTR + help + Set this to the default string for SELinux label. + +config SDFAT_SUPPORT_STLOG + bool "Enable storage log" + default y + depends on SDFAT_FS && PROC_STLOG + +config SDFAT_DEBUG + bool "enable debug features" + depends on SDFAT_FS + default y + +config SDFAT_DBG_IOCTL + bool "enable debug-ioctl features" + depends on SDFAT_FS && SDFAT_DEBUG + default n + +config SDFAT_DBG_MSG + bool "enable debug messages" + depends on SDFAT_FS && SDFAT_DEBUG + default y + +config SDFAT_DBG_BUGON + bool "enable strict BUG_ON() for debugging" + depends on SDFAT_FS && SDFAT_DEBUG + default n + +config SDFAT_DBG_WARNON + bool "enable strict WARN_ON() for debugging" + depends on SDFAT_FS && SDFAT_DEBUG + default n + +config SDFAT_STATISTICS + bool "enable statistics for bigdata" + depends on SDFAT_FS + default y diff --git a/fs/sdfat/LICENSE b/fs/sdfat/LICENSE new file mode 100644 index 0000000000000..d159169d10508 --- /dev/null +++ b/fs/sdfat/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/fs/sdfat/Makefile b/fs/sdfat/Makefile new file mode 100644 index 0000000000000..a5cd0858c6caa --- /dev/null +++ b/fs/sdfat/Makefile @@ -0,0 +1,24 @@ +# +# Makefile for the linux FAT12/16/32(VFAT)/64(exFAT) filesystem driver. +# + +obj-$(CONFIG_SDFAT_FS) += sdfat_fs.o + +sdfat_fs-objs := sdfat.o core.o core_fat.o core_exfat.o api.o blkdev.o \ + fatent.o amap_smart.o cache.o dfr.o nls.o misc.o \ + mpage.o extent.o + +sdfat_fs-$(CONFIG_SDFAT_VIRTUAL_XATTR) += xattr.o +sdfat_fs-$(CONFIG_SDFAT_STATISTICS) += statistics.o + + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + +cscope: + rm -rf cscope.files cscope.files + find $(PWD) \( -name '*.c' -o -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.s' -o -name '*.S' \) -print > cscope.files + cscope diff --git a/fs/sdfat/README.md b/fs/sdfat/README.md new file mode 100644 index 0000000000000..f291e8f1ae00b --- /dev/null +++ b/fs/sdfat/README.md @@ -0,0 +1,19 @@ +sdFAT FS support for Linux Kernel 4.4 +===================================== + +sdFAT is unified FAT-based file system which supports not only fat12/16/32 with +vfat but also exfat. sdFAT supports winnt short-name rule. + +Suggested Kernel config: + + CONFIG_SDFAT_FS=y + CONFIG_SDFAT_DELAYED_META_DIRTY=y + CONFIG_SDFAT_SUPPORT_DIR_SYNC=y + CONFIG_SDFAT_DEFAULT_CODEPAGE=437 + CONFIG_SDFAT_DEFAULT_IOCHARSET="utf8" + CONFIG_SDFAT_ALIGNED_MPAGE_WRITE=y + CONFIG_SDFAT_VIRTUAL_XATTR=y + CONFIG_SDFAT_VIRTUAL_XATTR_SELINUX_LABEL="u:object_r:vfat:s0" + CONFIG_SDFAT_DEBUG=y + CONFIG_SDFAT_DBG_MSG=y + CONFIG_SDFAT_STATISTICS=y diff --git a/fs/sdfat/amap_smart.c b/fs/sdfat/amap_smart.c new file mode 100644 index 0000000000000..b556f868d76e6 --- /dev/null +++ b/fs/sdfat/amap_smart.c @@ -0,0 +1,1314 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : amap_smart.c */ +/* PURPOSE : FAT32 Smart allocation code for sdFAT */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include + +#include "sdfat.h" +#include "core.h" +#include "amap_smart.h" + +/* AU list related functions */ +static inline void amap_list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + + /* Will be used to check if the entry is a single entry(selected) */ + entry->prev = NULL; + entry->next = NULL; +} + +static inline int amap_insert_to_list(AU_INFO_T *au, struct slist_head *shead) +{ + struct slist_head *entry = &au->shead; + + ASSERT(!entry->head); + + entry->next = shead->next; + entry->head = shead; + + shead->next = entry; + + return 0; +} + +static inline int amap_remove_from_list(AU_INFO_T *au, struct slist_head *shead) +{ + struct slist_head *entry = &au->shead; + struct slist_head *iter; + + BUG_ON(entry->head != shead); + + iter = shead; + + while (iter->next) { + if (iter->next == entry) { + // iter->next = iter->next->next + iter->next = entry->next; + + entry->next = NULL; + entry->head = NULL; + return 0; + } + iter = iter->next; + } + + BUG_ON("Not reachable"); +} + +/* Full-linear serach => Find AU with max. number of fclu */ +static inline AU_INFO_T *amap_find_hot_au_largest(struct slist_head *shead) +{ + struct slist_head *iter; + uint16_t max_fclu = 0; + AU_INFO_T *entry, *ret = NULL; + + ASSERT(shead->head == shead); /* Singly-list condition */ + ASSERT(shead->next != shead); + + iter = shead->next; + + while (iter) { + entry = list_entry(iter, AU_INFO_T, shead); + + if (entry->free_clusters > max_fclu) { + max_fclu = entry->free_clusters; + ret = entry; + } + + iter = iter->next; + } + + return ret; +} + +/* Find partially used AU with max. number of fclu. + * If there is no partial AU available, pick a clean one + */ +static inline AU_INFO_T *amap_find_hot_au_partial(AMAP_T *amap) +{ + struct slist_head *iter; + uint16_t max_fclu = 0; + AU_INFO_T *entry, *ret = NULL; + + iter = &amap->slist_hot; + ASSERT(iter->head == iter); /* Singly-list condition */ + ASSERT(iter->next != iter); + + iter = iter->next; + + while (iter) { + entry = list_entry(iter, AU_INFO_T, shead); + + if (entry->free_clusters > max_fclu) { + if (entry->free_clusters < amap->clusters_per_au) { + max_fclu = entry->free_clusters; + ret = entry; + } else { + if (!ret) + ret = entry; + } + } + + iter = iter->next; + } + + return ret; +} + + + + +/* + * Size-base AU management functions + */ + +/* + * Add au into cold AU MAP + * au: an isolated (not in a list) AU data structure + */ +int amap_add_cold_au(AMAP_T *amap, AU_INFO_T *au) +{ + FCLU_NODE_T *fclu_node = NULL; + + /* Check if a single entry */ + BUG_ON(au->head.prev); + + /* Ignore if the au is full */ + if (!au->free_clusters) + return 0; + + /* Find entry */ + fclu_node = NODE(au->free_clusters, amap); + + /* Insert to the list */ + list_add_tail(&(au->head), &(fclu_node->head)); + + /* Update fclu_hint (Increase) */ + if (au->free_clusters > amap->fclu_hint) + amap->fclu_hint = au->free_clusters; + + return 0; +} + +/* + * Remove an AU from AU MAP + */ +int amap_remove_cold_au(AMAP_T *amap, AU_INFO_T *au) +{ + struct list_head *prev = au->head.prev; + + /* Single entries are not managed in lists */ + if (!prev) { + BUG_ON(au->free_clusters > 0); + return 0; + } + + /* remove from list */ + amap_list_del(&(au->head)); + + return 0; +} + + +/* "Find" best fit AU + * returns NULL if there is no AU w/ enough free space. + * + * This function doesn't change AU status. + * The caller should call amap_remove_cold_au() if needed. + */ +AU_INFO_T *amap_find_cold_au_bestfit(AMAP_T *amap, uint16_t free_clusters) +{ + AU_INFO_T *au = NULL; + FCLU_NODE_T *fclu_iter; + + if (free_clusters <= 0 || free_clusters > amap->clusters_per_au) { + EMSG("AMAP: amap_find_cold_au_bestfit / unexpected arg. (%d)\n", + free_clusters); + return NULL; + } + + fclu_iter = NODE(free_clusters, amap); + + if (amap->fclu_hint < free_clusters) { + /* There is no AUs with enough free_clusters */ + return NULL; + } + + /* Naive Hash management (++) */ + do { + if (!list_empty(&fclu_iter->head)) { + struct list_head *first = fclu_iter->head.next; + + au = list_entry(first, AU_INFO_T, head); + + break; + } + + fclu_iter++; + } while (fclu_iter < (amap->fclu_nodes + amap->clusters_per_au)); + + + // BUG_ON(au->free_clusters < 0); + BUG_ON(au && (au->free_clusters > amap->clusters_per_au)); + + return au; +} + + +/* "Pop" best fit AU + * + * returns NULL if there is no AU w/ enough free space. + * The returned AU will not be in the list anymore. + */ +AU_INFO_T *amap_pop_cold_au_bestfit(AMAP_T *amap, uint16_t free_clusters) +{ + /* Naive implementation */ + AU_INFO_T *au; + + au = amap_find_cold_au_bestfit(amap, free_clusters); + if (au) + amap_remove_cold_au(amap, au); + + return au; +} + + + +/* Pop the AU with the largest free space + * + * search from 'start_fclu' to 0 + * (target freecluster : -1 for each step) + * start_fclu = 0 means to search from the max. value + */ +AU_INFO_T *amap_pop_cold_au_largest(AMAP_T *amap, uint16_t start_fclu) +{ + AU_INFO_T *au = NULL; + FCLU_NODE_T *fclu_iter; + + if (!start_fclu) + start_fclu = amap->clusters_per_au; + if (start_fclu > amap->clusters_per_au) + start_fclu = amap->clusters_per_au; + + /* Use hint (search start point) */ + if (amap->fclu_hint < start_fclu) + fclu_iter = NODE(amap->fclu_hint, amap); + else + fclu_iter = NODE(start_fclu, amap); + + /* Naive Hash management */ + do { + if (!list_empty(&fclu_iter->head)) { + struct list_head *first = fclu_iter->head.next; + + au = list_entry(first, AU_INFO_T, head); + // BUG_ON((au < amap->entries) || ((amap->entries + amap->n_au) <= au)); + + amap_list_del(first); + + // (Hint) Possible maximum value of free clusters (among cold) + /* if it wasn't the whole search, don't update fclu_hint */ + if (start_fclu == amap->clusters_per_au) + amap->fclu_hint = au->free_clusters; + + break; + } + + fclu_iter--; + } while (amap->fclu_nodes <= fclu_iter); + + return au; +} + + + +/* + * =============================================== + * Allocation Map related functions + * =============================================== + */ + +/* Create AMAP related data structure (mount time) */ +int amap_create(struct super_block *sb, u32 pack_ratio, u32 sect_per_au, u32 hidden_sect) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + AMAP_T *amap; + int total_used_clusters; + int n_au_table = 0; + int i, i_clu, i_au; + int i_au_root = -1, i_au_hot_from = INT_MAX; + u32 misaligned_sect = hidden_sect; + u64 tmp; + + BUG_ON(!fsi->bd_opened); + + if (fsi->amap) + return -EEXIST; + + /* Check conditions */ + if (fsi->vol_type != FAT32) { + sdfat_msg(sb, KERN_ERR, "smart allocation is only available " + "with fat32-fs"); + return -ENOTSUPP; + } + + if (fsi->num_sectors < AMAP_MIN_SUPPORT_SECTORS) { + sdfat_msg(sb, KERN_ERR, "smart allocation is only available " + "with sectors above %d", AMAP_MIN_SUPPORT_SECTORS); + return -ENOTSUPP; + } + + /* AU size must be a multiple of clu_size */ + if ((sect_per_au <= 0) || (sect_per_au & (fsi->sect_per_clus - 1))) { + sdfat_msg(sb, KERN_ERR, + "invalid AU size (sect_per_au : %u, " + "sect_per_clus : %u) " + "please re-format for performance.", + sect_per_au, fsi->sect_per_clus); + return -EINVAL; + } + + /* the start sector of this partition must be a multiple of clu_size */ + if (misaligned_sect & (fsi->sect_per_clus - 1)) { + sdfat_msg(sb, KERN_ERR, + "misaligned part (start sect : %u, " + "sect_per_clus : %u) " + "please re-format for performance.", + misaligned_sect, fsi->sect_per_clus); + return -EINVAL; + } + + /* data start sector must be a multiple of clu_size */ + if (fsi->data_start_sector & (fsi->sect_per_clus - 1)) { + sdfat_msg(sb, KERN_ERR, + "misaligned data area (start sect : %llu, " + "sect_per_clus : %u) " + "please re-format for performance.", + fsi->data_start_sector, fsi->sect_per_clus); + return -EINVAL; + } + + misaligned_sect &= (sect_per_au - 1); + + /* Allocate data structrues */ + amap = kzalloc(sizeof(AMAP_T), GFP_NOIO); + if (!amap) + return -ENOMEM; + + amap->sb = sb; + + tmp = fsi->num_sectors + misaligned_sect + sect_per_au - 1; + do_div(tmp, sect_per_au); + amap->n_au = tmp; + amap->n_clean_au = 0; + amap->n_full_au = 0; + + /* Reflect block-partition align first, + * then partition-data_start align + */ + amap->clu_align_bias = (misaligned_sect / fsi->sect_per_clus); + amap->clu_align_bias += (fsi->data_start_sector >> fsi->sect_per_clus_bits) - CLUS_BASE; + amap->clusters_per_au = sect_per_au / fsi->sect_per_clus; + + /* That is, + * the size of cluster is at least 4KB if the size of AU is 4MB + */ + if (amap->clusters_per_au > MAX_CLU_PER_AU) { + sdfat_log_msg(sb, KERN_INFO, + "too many clusters per AU (clus/au:%d > %d).", + amap->clusters_per_au, + MAX_CLU_PER_AU); + } + + /* is it needed? why here? */ + // set_sb_dirty(sb); + + spin_lock_init(&amap->amap_lock); + + amap->option.packing_ratio = pack_ratio; + amap->option.au_size = sect_per_au; + amap->option.au_align_factor = hidden_sect; + + + /* Allocate AU info table */ + n_au_table = (amap->n_au + N_AU_PER_TABLE - 1) / N_AU_PER_TABLE; + amap->au_table = kmalloc(sizeof(AU_INFO_T *) * n_au_table, GFP_NOIO); + if (!amap->au_table) { + sdfat_msg(sb, KERN_ERR, + "failed to alloc amap->au_table\n"); + kfree(amap); + return -ENOMEM; + } + + for (i = 0; i < n_au_table; i++) + amap->au_table[i] = (AU_INFO_T *)get_zeroed_page(GFP_NOIO); + + /* Allocate buckets indexed by # of free clusters */ + amap->fclu_order = get_order(sizeof(FCLU_NODE_T) * amap->clusters_per_au); + + // XXX: amap->clusters_per_au limitation is 512 (w/ 8 byte list_head) + sdfat_log_msg(sb, KERN_INFO, "page orders for AU nodes : %d " + "(clus_per_au : %d, node_size : %lu)", + amap->fclu_order, + amap->clusters_per_au, + (unsigned long)sizeof(FCLU_NODE_T)); + + if (!amap->fclu_order) + amap->fclu_nodes = (FCLU_NODE_T *)get_zeroed_page(GFP_NOIO); + else + amap->fclu_nodes = vzalloc(PAGE_SIZE << amap->fclu_order); + + amap->fclu_hint = amap->clusters_per_au; + + /* Hot AU list, ignored AU list */ + amap->slist_hot.next = NULL; + amap->slist_hot.head = &amap->slist_hot; + amap->total_fclu_hot = 0; + + amap->slist_ignored.next = NULL; + amap->slist_ignored.head = &amap->slist_ignored; + + /* Strategy related vars. */ + amap->cur_cold.au = NULL; + amap->cur_hot.au = NULL; + amap->n_need_packing = 0; + + + /* Build AMAP info */ + total_used_clusters = 0; // Count # of used clusters + + i_au_root = i_AU_of_CLU(amap, fsi->root_dir); + i_au_hot_from = amap->n_au - (SMART_ALLOC_N_HOT_AU - 1); + + for (i = 0; i < amap->clusters_per_au; i++) + INIT_LIST_HEAD(&amap->fclu_nodes[i].head); + + /* + * Thanks to kzalloc() + * amap->entries[i_au].free_clusters = 0; + * amap->entries[i_au].head.prev = NULL; + * amap->entries[i_au].head.next = NULL; + */ + + /* Parse FAT table */ + for (i_clu = CLUS_BASE; i_clu < fsi->num_clusters; i_clu++) { + u32 clu_data; + AU_INFO_T *au; + + if (fat_ent_get(sb, i_clu, &clu_data)) { + sdfat_msg(sb, KERN_ERR, + "failed to read fat entry(%u)\n", i_clu); + goto free_and_eio; + } + + if (IS_CLUS_FREE(clu_data)) { + au = GET_AU(amap, i_AU_of_CLU(amap, i_clu)); + au->free_clusters++; + } else + total_used_clusters++; + } + + /* Build AU list */ + for (i_au = 0; i_au < amap->n_au; i_au++) { + AU_INFO_T *au = GET_AU(amap, i_au); + + au->idx = i_au; + BUG_ON(au->free_clusters > amap->clusters_per_au); + + if (au->free_clusters == amap->clusters_per_au) + amap->n_clean_au++; + else if (au->free_clusters == 0) + amap->n_full_au++; + + /* If hot, insert to the hot list */ + if (i_au >= i_au_hot_from) { + amap_add_hot_au(amap, au); + amap->total_fclu_hot += au->free_clusters; + } else if (i_au != i_au_root || SMART_ALLOC_N_HOT_AU == 0) { + /* Otherwise, insert to the free cluster hash */ + amap_add_cold_au(amap, au); + } + } + + /* Hot list -> (root) -> (last) -> (last - 1) -> ... */ + if (i_au_root >= 0 && SMART_ALLOC_N_HOT_AU > 0) { + amap_add_hot_au(amap, GET_AU(amap, i_au_root)); + amap->total_fclu_hot += GET_AU(amap, i_au_root)->free_clusters; + } + + fsi->amap = amap; + fsi->used_clusters = total_used_clusters; + + sdfat_msg(sb, KERN_INFO, + "AMAP: Smart allocation enabled (opt : %u / %u / %u)", + amap->option.au_size, amap->option.au_align_factor, + amap->option.packing_ratio); + + /* Debug purpose - check */ + //{ + //u32 used_clusters; + //fat_count_used_clusters(sb, &used_clusters) + //ASSERT(used_clusters == total_used_clusters); + //} + + return 0; + + +free_and_eio: + if (amap) { + if (amap->au_table) { + for (i = 0; i < n_au_table; i++) + free_page((unsigned long)amap->au_table[i]); + kfree(amap->au_table); + } + if (amap->fclu_nodes) { + if (!amap->fclu_order) + free_page((unsigned long)amap->fclu_nodes); + else + vfree(amap->fclu_nodes); + } + kfree(amap); + } + return -EIO; +} + + +/* Free AMAP related structure */ +void amap_destroy(struct super_block *sb) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + int n_au_table; + + if (!amap) + return; + + DMSG("%s\n", __func__); + + n_au_table = (amap->n_au + N_AU_PER_TABLE - 1) / N_AU_PER_TABLE; + + if (amap->au_table) { + int i; + + for (i = 0; i < n_au_table; i++) + free_page((unsigned long)amap->au_table[i]); + + kfree(amap->au_table); + } + if (!amap->fclu_order) + free_page((unsigned long)amap->fclu_nodes); + else + vfree(amap->fclu_nodes); + kfree(amap); + SDFAT_SB(sb)->fsi.amap = NULL; +} + + +/* + * Check status of FS + * and change destination if needed to disable AU-aligned alloc. + * (from ALLOC_COLD_ALIGNED to ALLOC_COLD_SEQ) + */ +static inline int amap_update_dest(AMAP_T *amap, int ori_dest) +{ + FS_INFO_T *fsi = &(SDFAT_SB(amap->sb)->fsi); + int n_partial_au, n_partial_freeclus; + + if (ori_dest != ALLOC_COLD_ALIGNED) + return ori_dest; + + /* # of partial AUs and # of clusters in those AUs */ + n_partial_au = amap->n_au - amap->n_clean_au - amap->n_full_au; + n_partial_freeclus = fsi->num_clusters - fsi->used_clusters - + amap->clusters_per_au * amap->n_clean_au; + + /* Status of AUs : Full / Partial / Clean + * If there are many partial (and badly fragmented) AUs, + * the throughput will decrease extremly. + * + * The follow code will treat those worst cases. + */ + + /* XXX: AMAP heuristics */ + if ((amap->n_clean_au * 50 <= amap->n_au) && + (n_partial_freeclus*2) < (n_partial_au*amap->clusters_per_au)) { + /* If clean AUs are fewer than 2% of n_au (80 AUs per 16GB) + * and fragment ratio is more than 2 (AVG free_clusters=half AU) + * + * disable clean-first allocation + * enable VFAT-like sequential allocation + */ + return ALLOC_COLD_SEQ; + } + + return ori_dest; +} + + +#define PACKING_SOFTLIMIT (amap->option.packing_ratio) +#define PACKING_HARDLIMIT (amap->option.packing_ratio * 4) +/* + * Pick a packing AU if needed. + * Otherwise just return NULL + * + * This function includes some heuristics. + */ +static inline AU_INFO_T *amap_get_packing_au(AMAP_T *amap, int dest, int num_to_wb, int *clu_to_skip) +{ + AU_INFO_T *au = NULL; + + if (dest == ALLOC_COLD_PACKING) { + /* ALLOC_COLD_PACKING: + * Packing-first mode for defrag. + * Optimized to save clean AU + * + * 1) best-fit AU + * 2) Smallest AU (w/ minimum free clusters) + */ + if (num_to_wb >= amap->clusters_per_au) + num_to_wb = num_to_wb % amap->clusters_per_au; + + /* 이거 주석처리하면, AU size 딱 맞을때는 clean, 나머지는 작은거부터 */ + if (num_to_wb == 0) + num_to_wb = 1; // Don't use clean AUs + + au = amap_find_cold_au_bestfit(amap, num_to_wb); + if (au && au->free_clusters == amap->clusters_per_au && num_to_wb > 1) { + /* if au is clean then get a new partial one */ + au = amap_find_cold_au_bestfit(amap, 1); + } + + if (au) { + amap->n_need_packing = 0; + amap_remove_cold_au(amap, au); + return au; + } + } + + + /* Heuristic packing: + * This will improve QoS greatly. + * + * Count # of AU_ALIGNED allocation. + * If the number exceeds the specific threshold, + * allocate on a partial AU or generate random I/O. + */ + if ((PACKING_SOFTLIMIT > 0) && + (amap->n_need_packing >= PACKING_SOFTLIMIT) && + (num_to_wb < (int)amap->clusters_per_au)) { + /* Best-fit packing: + * If num_to_wb (expected number to be allocated) is smaller + * than AU_SIZE, find a best-fit AU. + */ + + /* Back margin (heuristics) */ + if (num_to_wb < amap->clusters_per_au / 4) + num_to_wb = amap->clusters_per_au / 4; + + au = amap_find_cold_au_bestfit(amap, num_to_wb); + if (au != NULL) { + amap_remove_cold_au(amap, au); + + MMSG("AMAP: packing (cnt: %d) / softlimit, " + "best-fit (num_to_wb: %d))\n", + amap->n_need_packing, num_to_wb); + + if (au->free_clusters > num_to_wb) { // Best-fit search: if 문 무조건 hit + *clu_to_skip = au->free_clusters - num_to_wb; + /* otherwise don't skip */ + } + amap->n_need_packing = 0; + return au; + } + } + + if ((PACKING_HARDLIMIT) && amap->n_need_packing >= PACKING_HARDLIMIT) { + /* Compulsory SLC flushing: + * If there was no chance to do best-fit packing + * and the # of AU-aligned allocation exceeds HARD threshold, + * then pick a clean AU and generate a compulsory random I/O. + */ + au = amap_pop_cold_au_largest(amap, amap->clusters_per_au); + if (au) { + MMSG("AMAP: packing (cnt: %d) / hard-limit, largest)\n", + amap->n_need_packing); + + if (au->free_clusters >= 96) { + *clu_to_skip = au->free_clusters / 2; + MMSG("AMAP: cluster idx re-position\n"); + } + amap->n_need_packing = 0; + return au; + } + } + + /* Update # of clean AU allocation */ + amap->n_need_packing++; + return NULL; +} + + +/* Pick a target AU: + * This function should be called + * only if there are one or more free clusters in the bdev. + */ +TARGET_AU_T *amap_get_target_au(AMAP_T *amap, int dest, int num_to_wb) +{ + int loop_count = 0; + +retry: + if (++loop_count >= 3) { + /* No space available (or AMAP consistency error) + * This could happen because of the ignored AUs but not likely + * (because the defrag daemon will not work if there is no enough space) + */ + BUG_ON(amap->slist_ignored.next == NULL); + return NULL; + } + + /* Hot clusters (DIR) */ + if (dest == ALLOC_HOT) { + + /* Working hot AU exist? */ + if (amap->cur_hot.au == NULL || amap->cur_hot.au->free_clusters == 0) { + AU_INFO_T *au; + + if (amap->total_fclu_hot == 0) { + /* No more hot AU avaialbe */ + dest = ALLOC_COLD; + + goto retry; + } + + au = amap_find_hot_au_partial(amap); + + BUG_ON(au == NULL); + BUG_ON(au->free_clusters <= 0); + + amap->cur_hot.au = au; + amap->cur_hot.idx = 0; + amap->cur_hot.clu_to_skip = 0; + } + + /* Now allocate on a hot AU */ + return &amap->cur_hot; + } + + /* Cold allocation: + * If amap->cur_cold.au has one or more free cluster(s), + * then just return amap->cur_cold + */ + if ((!amap->cur_cold.au) + || (amap->cur_cold.idx == amap->clusters_per_au) + || (amap->cur_cold.au->free_clusters == 0)) { + + AU_INFO_T *au = NULL; + const AU_INFO_T *old_au = amap->cur_cold.au; + int n_clu_to_skip = 0; + + if (old_au) { + ASSERT(!IS_AU_WORKING(old_au, amap)); + /* must be NOT WORKING AU. + * (only for information gathering) + */ + } + + /* Next target AU is needed: + * There are 3 possible ALLOC options for cold AU + * + * ALLOC_COLD_ALIGNED: Clean AU first, but heuristic packing is ON + * ALLOC_COLD_PACKING: Packing AU first (usually for defrag) + * ALLOC_COLD_SEQ : Sequential AU allocation (VFAT-like) + */ + + /* Experimental: Modify allocation destination if needed (ALIGNED => SEQ) */ + // dest = amap_update_dest(amap, dest); + + if ((dest == ALLOC_COLD_SEQ) && old_au) { + int i_au = old_au->idx + 1; + + while (i_au != old_au->idx) { + au = GET_AU(amap, i_au); + + if ((au->free_clusters > 0) && + !IS_AU_HOT(au, amap) && + !IS_AU_IGNORED(au, amap)) { + MMSG("AMAP: new cold AU(%d) with %d " + "clusters (seq)\n", + au->idx, au->free_clusters); + + amap_remove_cold_au(amap, au); + goto ret_new_cold; + } + i_au++; + if (i_au >= amap->n_au) + i_au = 0; + } + + // no cold AUs are available => Hot allocation + dest = ALLOC_HOT; + goto retry; + } + + + /* + * Check if packing is needed + * (ALLOC_COLD_PACKING is treated by this function) + */ + au = amap_get_packing_au(amap, dest, num_to_wb, &n_clu_to_skip); + if (au) { + MMSG("AMAP: new cold AU(%d) with %d clusters " + "(packing)\n", au->idx, au->free_clusters); + goto ret_new_cold; + } + + /* ALLOC_COLD_ALIGNED */ + /* Check if the adjacent AU is clean */ + if (old_au && ((old_au->idx + 1) < amap->n_au)) { + au = GET_AU(amap, old_au->idx + 1); + if ((au->free_clusters == amap->clusters_per_au) && + !IS_AU_HOT(au, amap) && + !IS_AU_IGNORED(au, amap)) { + MMSG("AMAP: new cold AU(%d) with %d clusters " + "(adjacent)\n", au->idx, au->free_clusters); + amap_remove_cold_au(amap, au); + goto ret_new_cold; + } + } + + /* Clean or largest AU */ + au = amap_pop_cold_au_largest(amap, 0); + if (!au) { + //ASSERT(amap->total_fclu_hot == (fsi->num_clusters - fsi->used_clusters - 2)); + dest = ALLOC_HOT; + goto retry; + } + + MMSG("AMAP: New cold AU (%d) with %d clusters\n", + au->idx, au->free_clusters); + +ret_new_cold: + SET_AU_WORKING(au); + + amap->cur_cold.au = au; + amap->cur_cold.idx = 0; + amap->cur_cold.clu_to_skip = n_clu_to_skip; + } + + return &amap->cur_cold; +} + +/* Put and update target AU */ +void amap_put_target_au(AMAP_T *amap, TARGET_AU_T *cur, unsigned int num_allocated) +{ + /* Update AMAP info vars. */ + if (num_allocated > 0 && + (cur->au->free_clusters + num_allocated) == amap->clusters_per_au) { + /* if the target AU was a clean AU before this allocation ... */ + amap->n_clean_au--; + } + if (num_allocated > 0 && + cur->au->free_clusters == 0) + amap->n_full_au++; + + if (IS_AU_HOT(cur->au, amap)) { + /* Hot AU */ + MMSG("AMAP: hot allocation at AU %d\n", cur->au->idx); + amap->total_fclu_hot -= num_allocated; + + /* Intra-AU round-robin */ + if (cur->idx >= amap->clusters_per_au) + cur->idx = 0; + + /* No more space available */ + if (cur->au->free_clusters == 0) + cur->au = NULL; + + } else { + /* non-hot AU */ + ASSERT(IS_AU_WORKING(cur->au, amap)); + + if (cur->idx >= amap->clusters_per_au || cur->au->free_clusters == 0) { + /* It should be inserted back to AU MAP */ + cur->au->shead.head = NULL; // SET_AU_NOT_WORKING + amap_add_cold_au(amap, cur->au); + + // cur->au = NULL; // This value will be used for the next AU selection + cur->idx = amap->clusters_per_au; // AU closing + } + } + +} + + +/* Reposition target->idx for packing (Heuristics): + * Skip (num_to_skip) free clusters in (cur->au) + */ +static inline int amap_skip_cluster(struct super_block *sb, TARGET_AU_T *cur, int num_to_skip) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + u32 clu, read_clu; + MMSG_VAR(int num_to_skip_orig = num_to_skip); + + if (num_to_skip >= cur->au->free_clusters) { + EMSG("AMAP(%s): skip mis-use. amap_566\n", __func__); + return -EIO; + } + + clu = CLU_of_i_AU(amap, cur->au->idx, cur->idx); + while (num_to_skip > 0) { + if (clu >= CLUS_BASE) { + /* Cf. + * If AMAP's integrity is okay, + * we don't need to check if (clu < fsi->num_clusters) + */ + + if (fat_ent_get(sb, clu, &read_clu)) + return -EIO; + + if (IS_CLUS_FREE(read_clu)) + num_to_skip--; + } + + // Move clu->idx + clu++; + (cur->idx)++; + + if (cur->idx >= amap->clusters_per_au) { + /* End of AU (Not supposed) */ + EMSG("AMAP: Skip - End of AU?! (amap_596)\n"); + cur->idx = 0; + return -EIO; + } + } + + MMSG("AMAP: Skip_clusters (%d skipped => %d, among %d free clus)\n", + num_to_skip_orig, cur->idx, cur->au->free_clusters); + + return 0; +} + + +/* AMAP-based allocation function for FAT32 */ +s32 amap_fat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + TARGET_AU_T *cur = NULL; + AU_INFO_T *target_au = NULL; /* Allocation target AU */ + s32 ret = -ENOSPC; + u32 last_clu = CLUS_EOF, read_clu; + u32 new_clu, total_cnt; + u32 num_allocated = 0, num_allocated_each = 0; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + BUG_ON(!amap); + BUG_ON(IS_CLUS_EOF(fsi->used_clusters)); + + total_cnt = fsi->num_clusters - CLUS_BASE; + + if (unlikely(total_cnt < fsi->used_clusters)) { + sdfat_fs_error_ratelimit(sb, + "AMAP(%s): invalid used clusters(t:%u,u:%u)\n", + __func__, total_cnt, fsi->used_clusters); + return -EIO; + } + + if (num_alloc > total_cnt - fsi->used_clusters) + return -ENOSPC; + + p_chain->dir = CLUS_EOF; + + set_sb_dirty(sb); + + // spin_lock(&amap->amap_lock); + +retry_alloc: + /* Allocation strategy implemented */ + cur = amap_get_target_au(amap, dest, fsi->reserved_clusters); + if (unlikely(!cur)) { + // There is no available AU (only ignored-AU are left) + sdfat_msg(sb, KERN_ERR, "AMAP Allocator: no avaialble AU."); + goto error; + } + + /* If there are clusters to skip */ + if (cur->clu_to_skip > 0) { + if (amap_skip_cluster(sb, &amap->cur_cold, cur->clu_to_skip)) { + ret = -EIO; + goto error; + } + cur->clu_to_skip = 0; + } + + target_au = cur->au; + + /* + * cur->au : target AU info pointer + * cur->idx : the intra-cluster idx in the AU to start from + */ + BUG_ON(!cur->au); + BUG_ON(!cur->au->free_clusters); + BUG_ON(cur->idx >= amap->clusters_per_au); + + num_allocated_each = 0; + new_clu = CLU_of_i_AU(amap, target_au->idx, cur->idx); + + do { + /* Allocate at the target AU */ + if ((new_clu >= CLUS_BASE) && (new_clu < fsi->num_clusters)) { + if (fat_ent_get(sb, new_clu, &read_clu)) { + // spin_unlock(&amap->amap_lock); + ret = -EIO; + goto error; + } + + if (IS_CLUS_FREE(read_clu)) { + BUG_ON(GET_AU(amap, i_AU_of_CLU(amap, new_clu)) != target_au); + + /* Free cluster found */ + if (fat_ent_set(sb, new_clu, CLUS_EOF)) { + ret = -EIO; + goto error; + } + + num_allocated_each++; + + if (IS_CLUS_EOF(p_chain->dir)) { + p_chain->dir = new_clu; + } else { + if (fat_ent_set(sb, last_clu, new_clu)) { + ret = -EIO; + goto error; + } + } + last_clu = new_clu; + + /* Update au info */ + target_au->free_clusters--; + } + + } + + new_clu++; + (cur->idx)++; + + /* End of the AU */ + if ((cur->idx >= amap->clusters_per_au) || !(target_au->free_clusters)) + break; + } while (num_allocated_each < num_alloc); + + /* Update strategy info */ + amap_put_target_au(amap, cur, num_allocated_each); + + + num_allocated += num_allocated_each; + fsi->used_clusters += num_allocated_each; + num_alloc -= num_allocated_each; + + + if (num_alloc > 0) + goto retry_alloc; + + // spin_unlock(&amap->amap_lock); + return 0; +error: + if (num_allocated) + fsi->fs_func->free_cluster(sb, p_chain, 0); + return ret; +} + + +/* Free cluster for FAT32 (not implemented yet) */ +s32 amap_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse) +{ + return -ENOTSUPP; +} + + +/* + * This is called by fat_free_cluster() + * to update AMAP info. + */ +s32 amap_release_cluster(struct super_block *sb, u32 clu) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + AU_INFO_T *au; + int i_au; + + // spin_lock(&amap->amap_lock); + + /* Update AU info */ + i_au = i_AU_of_CLU(amap, clu); + BUG_ON(i_au >= amap->n_au); + au = GET_AU(amap, i_au); + if (au->free_clusters >= amap->clusters_per_au) { + sdfat_fs_error(sb, "%s, au->free_clusters(%hd) is " + "greater than or equal to amap->clusters_per_au(%hd)", + __func__, au->free_clusters, amap->clusters_per_au); + return -EIO; + } + + if (IS_AU_HOT(au, amap)) { + MMSG("AMAP: Hot cluster freed\n"); + au->free_clusters++; + amap->total_fclu_hot++; + } else if (!IS_AU_WORKING(au, amap) && !IS_AU_IGNORED(au, amap)) { + /* Ordinary AU - update AU tree */ + // Can be optimized by implementing amap_update_au + amap_remove_cold_au(amap, au); + au->free_clusters++; + amap_add_cold_au(amap, au); + } else + au->free_clusters++; + + + /* Update AMAP info */ + if (au->free_clusters == amap->clusters_per_au) + amap->n_clean_au++; + if (au->free_clusters == 1) + amap->n_full_au--; + + // spin_unlock(&amap->amap_lock); + return 0; +} + + +/* + * Check if the cluster is in a working AU + * The caller should hold sb lock. + * This func. should be used only if smart allocation is on + */ +s32 amap_check_working(struct super_block *sb, u32 clu) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + AU_INFO_T *au; + + BUG_ON(!amap); + au = GET_AU(amap, i_AU_of_CLU(amap, clu)); + return IS_AU_WORKING(au, amap); +} + + +/* + * Return the # of free clusters in that AU + */ +s32 amap_get_freeclus(struct super_block *sb, u32 clu) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + AU_INFO_T *au; + + BUG_ON(!amap); + au = GET_AU(amap, i_AU_of_CLU(amap, clu)); + return (s32)au->free_clusters; +} + + +/* + * Add the AU containing 'clu' to the ignored AU list. + * The AU will not be used by the allocator. + * + * XXX: Ignored counter needed + */ +s32 amap_mark_ignore(struct super_block *sb, u32 clu) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + AU_INFO_T *au; + + BUG_ON(!amap); + au = GET_AU(amap, i_AU_of_CLU(amap, clu)); + + if (IS_AU_HOT(au, amap)) { + /* Doesn't work with hot AUs */ + return -EPERM; + } else if (IS_AU_WORKING(au, amap)) { + return -EBUSY; + } + + //BUG_ON(IS_AU_IGNORED(au, amap) && (GET_IGN_CNT(au) == 0)); + if (IS_AU_IGNORED(au, amap)) + return 0; + + amap_remove_cold_au(amap, au); + amap_insert_to_list(au, &amap->slist_ignored); + + BUG_ON(!IS_AU_IGNORED(au, amap)); + + //INC_IGN_CNT(au); + MMSG("AMAP: Mark ignored AU (%d)\n", au->idx); + return 0; +} + + +/* + * This function could be used only on IGNORED AUs. + * The caller should care whether it's ignored or not before using this func. + */ +s32 amap_unmark_ignore(struct super_block *sb, u32 clu) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + AU_INFO_T *au; + + BUG_ON(!amap); + + au = GET_AU(amap, i_AU_of_CLU(amap, clu)); + + BUG_ON(!IS_AU_IGNORED(au, amap)); + // BUG_ON(GET_IGN_CNT(au) == 0); + + amap_remove_from_list(au, &amap->slist_ignored); + amap_add_cold_au(amap, au); + + BUG_ON(IS_AU_IGNORED(au, amap)); + + //DEC_IGN_CNT(au); + + MMSG("AMAP: Unmark ignored AU (%d)\n", au->idx); + + return 0; +} + +/* + * Unmark all ignored AU + * This will return # of unmarked AUs + */ +s32 amap_unmark_ignore_all(struct super_block *sb) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + struct slist_head *entry; + AU_INFO_T *au; + int n = 0; + + BUG_ON(!amap); + entry = amap->slist_ignored.next; + while (entry) { + au = list_entry(entry, AU_INFO_T, shead); + + BUG_ON(au != GET_AU(amap, au->idx)); + BUG_ON(!IS_AU_IGNORED(au, amap)); + + //CLEAR_IGN_CNT(au); + amap_remove_from_list(au, &amap->slist_ignored); + amap_add_cold_au(amap, au); + + MMSG("AMAP: Unmark ignored AU (%d)\n", au->idx); + n++; + + entry = amap->slist_ignored.next; + } + + BUG_ON(amap->slist_ignored.next != NULL); + MMSG("AMAP: unmark_ignore_all, total %d AUs\n", n); + + return n; +} + +/** + * @fn amap_get_au_stat + * @brief report AUs status depending on mode + * @return positive on success, 0 otherwise + * @param sbi super block info + * @param mode TOTAL, CLEAN and FULL + */ +u32 amap_get_au_stat(struct super_block *sb, s32 mode) +{ + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + + if (!amap) + return 0; + + if (mode == VOL_AU_STAT_TOTAL) + return amap->n_au; + else if (mode == VOL_AU_STAT_CLEAN) + return amap->n_clean_au; + else if (mode == VOL_AU_STAT_FULL) + return amap->n_full_au; + + return 0; +} + diff --git a/fs/sdfat/amap_smart.h b/fs/sdfat/amap_smart.h new file mode 100644 index 0000000000000..caee6f6b3681d --- /dev/null +++ b/fs/sdfat/amap_smart.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _SDFAT_AMAP_H +#define _SDFAT_AMAP_H + +#include +#include +#include + +/* AMAP Configuration Variable */ +#define SMART_ALLOC_N_HOT_AU (5) + +/* Allocating Destination (for smart allocator): + * moved to sdfat.h + */ +/* + * #define ALLOC_COLD_ALIGNED (1) + * #define ALLOC_COLD_PACKING (2) + * #define ALLOC_COLD_SEQ (4) + */ + +/* Minimum sectors for support AMAP create */ +#define AMAP_MIN_SUPPORT_SECTORS (1048576) + +#define amap_add_hot_au(amap, au) amap_insert_to_list(au, &amap->slist_hot) + +/* singly linked list */ +struct slist_head { + struct slist_head *next; + struct slist_head *head; +}; + +/* AU entry type */ +typedef struct __AU_INFO_T { + uint16_t idx; /* the index of the AU (0, 1, 2, ... ) */ + uint16_t free_clusters; /* # of available cluster */ + union { + struct list_head head; + struct slist_head shead;/* singly linked list head for hot list */ + }; +} AU_INFO_T; + + +/* Allocation Target AU */ +typedef struct __TARGET_AU_T { + AU_INFO_T *au; /* Working AU */ + uint16_t idx; /* Intra-AU cluster index */ + uint16_t clu_to_skip; /* Clusters to skip */ +} TARGET_AU_T; + + +/* AMAP free-clusters-based node */ +typedef struct { + struct list_head head; /* the list of AUs */ +} FCLU_NODE_T; + + +/* AMAP options */ +typedef struct { + unsigned int packing_ratio; /* Tunable packing ratio */ + unsigned int au_size; /* AU size in sectors */ + unsigned int au_align_factor; /* Hidden sectors % au_size */ +} AMAP_OPT_T; + +typedef struct __AMAP_T { + spinlock_t amap_lock; /* obsolete */ + struct super_block *sb; + + int n_au; + int n_clean_au, n_full_au; + int clu_align_bias; + uint16_t clusters_per_au; + AU_INFO_T **au_table; /* An array of AU_INFO entries */ + AMAP_OPT_T option; + + /* Size-based AU management pool (cold) */ + FCLU_NODE_T *fclu_nodes; /* An array of listheads */ + int fclu_order; /* Page order that fclu_nodes needs */ + int fclu_hint; /* maximum # of free clusters in an AU */ + + /* Hot AU list */ + unsigned int total_fclu_hot; /* Free clusters in hot list */ + struct slist_head slist_hot; /* Hot AU list */ + + /* Ignored AU list */ + struct slist_head slist_ignored; + + /* Allocator variables (keep 2 AUs at maximum) */ + TARGET_AU_T cur_cold; + TARGET_AU_T cur_hot; + int n_need_packing; +} AMAP_T; + + +/* AU table */ +#define N_AU_PER_TABLE (int)(PAGE_SIZE / sizeof(AU_INFO_T)) +#define GET_AU(amap, i_AU) (amap->au_table[(i_AU) / N_AU_PER_TABLE] + ((i_AU) % N_AU_PER_TABLE)) +//#define MAX_CLU_PER_AU (int)(PAGE_SIZE / sizeof(FCLU_NODE_T)) +#define MAX_CLU_PER_AU (1024) + +/* Cold AU bucket <-> # of freeclusters */ +#define NODE_CLEAN(amap) (&amap->fclu_nodes[amap->clusters_per_au - 1]) +#define NODE(fclu, amap) (&amap->fclu_nodes[fclu - 1]) +#define FREE_CLUSTERS(node, amap) ((int)(node - amap->fclu_nodes) + 1) + +/* AU status */ +#define MAGIC_WORKING ((struct slist_head *)0xFFFF5091) +#define IS_AU_HOT(au, amap) (au->shead.head == &amap->slist_hot) +#define IS_AU_IGNORED(au, amap) (au->shead.head == &amap->slist_ignored) +#define IS_AU_WORKING(au, amap) (au->shead.head == MAGIC_WORKING) +#define SET_AU_WORKING(au) (au->shead.head = MAGIC_WORKING) + +/* AU <-> cluster */ +#define i_AU_of_CLU(amap, clu) ((amap->clu_align_bias + clu) / amap->clusters_per_au) +#define CLU_of_i_AU(amap, i_au, idx) \ + ((uint32_t)(i_au) * (uint32_t)amap->clusters_per_au + (idx) - amap->clu_align_bias) + +/* + * NOTE : AMAP internal functions are moved to core.h + */ + +#endif /* _SDFAT_AMAP_H */ diff --git a/fs/sdfat/api.c b/fs/sdfat/api.c new file mode 100644 index 0000000000000..02e0939efeecd --- /dev/null +++ b/fs/sdfat/api.c @@ -0,0 +1,629 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : sdfat_api.c */ +/* PURPOSE : sdFAT volume lock layer */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include + +#include "version.h" +#include "config.h" + +#include "sdfat.h" +#include "core.h" + +/*----------------------------------------------------------------------*/ +/* Internal structures */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ +static DEFINE_MUTEX(_lock_core); + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Local Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Local Function Declarations */ +/*----------------------------------------------------------------------*/ + +/*======================================================================*/ +/* Global Function Definitions */ +/* - All functions for global use have same return value format, */ +/* that is, 0 on success and minus error number on */ +/* various error condition. */ +/*======================================================================*/ + +/*----------------------------------------------------------------------*/ +/* sdFAT Filesystem Init & Exit Functions */ +/*----------------------------------------------------------------------*/ + +s32 fsapi_init(void) +{ + return fscore_init(); +} + +s32 fsapi_shutdown(void) +{ + return fscore_shutdown(); +} + +/*----------------------------------------------------------------------*/ +/* Volume Management Functions */ +/*----------------------------------------------------------------------*/ + +/* mount the file system volume */ +s32 fsapi_mount(struct super_block *sb) +{ + s32 err; + + /* acquire the core lock for file system ccritical section */ + mutex_lock(&_lock_core); + + err = meta_cache_init(sb); + if (err) + goto out; + + err = fscore_mount(sb); +out: + if (err) + meta_cache_shutdown(sb); + + /* release the core lock for file system critical section */ + mutex_unlock(&_lock_core); + + return err; +} +EXPORT_SYMBOL(fsapi_mount); + +/* unmount the file system volume */ +s32 fsapi_umount(struct super_block *sb) +{ + s32 err; + + /* acquire the core lock for file system ccritical section */ + mutex_lock(&_lock_core); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_umount(sb); + meta_cache_shutdown(sb); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + + /* release the core lock for file system critical section */ + mutex_unlock(&_lock_core); + + return err; +} +EXPORT_SYMBOL(fsapi_umount); + +/* get the information of a file system volume */ +s32 fsapi_statfs(struct super_block *sb, VOL_INFO_T *info) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* check the validity of pointer parameters */ + ASSERT(info); + + if (fsi->used_clusters == (u32) ~0) { + s32 err; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_statfs(sb, info); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; + } + + info->FatType = fsi->vol_type; + info->ClusterSize = fsi->cluster_size; + info->NumClusters = fsi->num_clusters - 2; /* clu 0 & 1 */ + info->UsedClusters = fsi->used_clusters + fsi->reserved_clusters; + info->FreeClusters = info->NumClusters - info->UsedClusters; + + return 0; +} +EXPORT_SYMBOL(fsapi_statfs); + +/* synchronize a file system volume */ +s32 fsapi_sync_fs(struct super_block *sb, s32 do_sync) +{ + s32 err; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_sync_fs(sb, do_sync); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_sync_fs); + +s32 fsapi_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) +{ + s32 err; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_set_vol_flags(sb, new_flag, always_sync); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_set_vol_flags); + +/*----------------------------------------------------------------------*/ +/* File Operation Functions */ +/*----------------------------------------------------------------------*/ + +/* lookup */ +s32 fsapi_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && path); + + if (unlikely(!strlen(path))) + return -EINVAL; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_lookup(inode, path, fid); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_lookup); + +/* create a file */ +s32 fsapi_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && path); + + if (unlikely(!strlen(path))) + return -EINVAL; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_create(inode, path, mode, fid); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_create); + +/* read the target string of symlink */ +s32 fsapi_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && buffer); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_read_link(inode, fid, buffer, count, rcount); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_read_link); + +/* write the target string of symlink */ +s32 fsapi_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && buffer); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_write_link(inode, fid, buffer, count, wcount); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_write_link); + +/* resize the file length */ +s32 fsapi_truncate(struct inode *inode, u64 old_size, u64 new_size) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + TMSG("%s entered (inode %p size %llu)\n", __func__, inode, new_size); + err = fscore_truncate(inode, old_size, new_size); + TMSG("%s exitted (%d)\n", __func__, err); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_truncate); + +/* rename or move a old file into a new file */ +s32 fsapi_rename(struct inode *old_parent_inode, FILE_ID_T *fid, + struct inode *new_parent_inode, struct dentry *new_dentry) +{ + s32 err; + struct super_block *sb = old_parent_inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_rename(old_parent_inode, fid, new_parent_inode, new_dentry); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_rename); + +/* remove a file */ +s32 fsapi_remove(struct inode *inode, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_remove(inode, fid); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_remove); + +/* get the information of a given file */ +s32 fsapi_read_inode(struct inode *inode, DIR_ENTRY_T *info) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + TMSG("%s entered (inode %p info %p\n", __func__, inode, info); + err = fscore_read_inode(inode, info); + TMSG("%s exited (err:%d)\n", __func__, err); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_read_inode); + +/* set the information of a given file */ +s32 fsapi_write_inode(struct inode *inode, DIR_ENTRY_T *info, int sync) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + TMSG("%s entered (inode %p info %p sync:%d\n", + __func__, inode, info, sync); + err = fscore_write_inode(inode, info, sync); + TMSG("%s exited (err:%d)\n", __func__, err); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_write_inode); + +/* return the cluster number in the given cluster offset */ +s32 fsapi_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(clu); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + TMSG("%s entered (inode:%p clus:%08x dest:%d\n", + __func__, inode, *clu, dest); + err = fscore_map_clus(inode, clu_offset, clu, dest); + TMSG("%s exited (clu:%08x err:%d)\n", __func__, *clu, err); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_map_clus); + +/* reserve a cluster */ +s32 fsapi_reserve_clus(struct inode *inode) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + TMSG("%s entered (inode:%p)\n", __func__, inode); + err = fscore_reserve_clus(inode); + TMSG("%s exited (err:%d)\n", __func__, err); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_reserve_clus); + +/*----------------------------------------------------------------------*/ +/* Directory Operation Functions */ +/*----------------------------------------------------------------------*/ + +/* create(make) a directory */ +s32 fsapi_mkdir(struct inode *inode, u8 *path, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && path); + + if (unlikely(!strlen(path))) + return -EINVAL; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_mkdir(inode, path, fid); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_mkdir); + +/* read a directory entry from the opened directory */ +s32 fsapi_readdir(struct inode *inode, DIR_ENTRY_T *dir_entry) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(dir_entry); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_readdir(inode, dir_entry); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_readdir); + +/* remove a directory */ +s32 fsapi_rmdir(struct inode *inode, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_rmdir(inode, fid); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_rmdir); + +/* unlink a file. + * that is, remove an entry from a directory. BUT don't truncate + */ +s32 fsapi_unlink(struct inode *inode, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid); + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = fscore_unlink(inode, fid); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_unlink); + +/* reflect the internal dirty flags to VFS bh dirty flags */ +s32 fsapi_cache_flush(struct super_block *sb, int do_sync) +{ + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + fcache_flush(sb, do_sync); + dcache_flush(sb, do_sync); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return 0; +} +EXPORT_SYMBOL(fsapi_cache_flush); + +/* release FAT & buf cache */ +s32 fsapi_cache_release(struct super_block *sb) +{ +#ifdef CONFIG_SDFAT_DEBUG + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + + fcache_release_all(sb); + dcache_release_all(sb); + + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); +#endif /* CONFIG_SDFAT_DEBUG */ + return 0; +} +EXPORT_SYMBOL(fsapi_cache_release); + +u32 fsapi_get_au_stat(struct super_block *sb, s32 mode) +{ + /* volume lock is not required */ + return fscore_get_au_stat(sb, mode); +} +EXPORT_SYMBOL(fsapi_get_au_stat); + +/* clear extent cache */ +void fsapi_invalidate_extent(struct inode *inode) +{ + /* Volume lock is not required, + * because it is only called by evict_inode. + * If any other function can call it, + * you should check whether volume lock is needed or not. + */ + extent_cache_inval_inode(inode); +} +EXPORT_SYMBOL(fsapi_invalidate_extent); + + + +#ifdef CONFIG_SDFAT_DFR +/*----------------------------------------------------------------------*/ +/* Defragmentation related */ +/*----------------------------------------------------------------------*/ +s32 fsapi_dfr_get_info(struct super_block *sb, void *arg) +{ + /* volume lock is not required */ + return defrag_get_info(sb, (struct defrag_info_arg *)arg); +} +EXPORT_SYMBOL(fsapi_dfr_get_info); + +s32 fsapi_dfr_scan_dir(struct super_block *sb, void *args) +{ + s32 err; + + /* check the validity of pointer parameters */ + ASSERT(args); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = defrag_scan_dir(sb, (struct defrag_trav_arg *)args); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_dfr_scan_dir); + +s32 fsapi_dfr_validate_clus(struct inode *inode, void *chunk, int skip_prev) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = defrag_validate_cluster(inode, + (struct defrag_chunk_info *)chunk, skip_prev); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_dfr_validate_clus); + +s32 fsapi_dfr_reserve_clus(struct super_block *sb, s32 nr_clus) +{ + s32 err; + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = defrag_reserve_clusters(sb, nr_clus); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + return err; +} +EXPORT_SYMBOL(fsapi_dfr_reserve_clus); + +s32 fsapi_dfr_mark_ignore(struct super_block *sb, unsigned int clus) +{ + /* volume lock is not required */ + return defrag_mark_ignore(sb, clus); +} +EXPORT_SYMBOL(fsapi_dfr_mark_ignore); + +void fsapi_dfr_unmark_ignore_all(struct super_block *sb) +{ + /* volume lock is not required */ + defrag_unmark_ignore_all(sb); +} +EXPORT_SYMBOL(fsapi_dfr_unmark_ignore_all); + +s32 fsapi_dfr_map_clus(struct inode *inode, u32 clu_offset, u32 *clu) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(clu); + + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + err = defrag_map_cluster(inode, clu_offset, clu); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); + + return err; +} +EXPORT_SYMBOL(fsapi_dfr_map_clus); + +void fsapi_dfr_writepage_endio(struct page *page) +{ + /* volume lock is not required */ + defrag_writepage_end_io(page); +} +EXPORT_SYMBOL(fsapi_dfr_writepage_endio); + +void fsapi_dfr_update_fat_prev(struct super_block *sb, int force) +{ + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + defrag_update_fat_prev(sb, force); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); +} +EXPORT_SYMBOL(fsapi_dfr_update_fat_prev); + +void fsapi_dfr_update_fat_next(struct super_block *sb) +{ + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + defrag_update_fat_next(sb); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); +} +EXPORT_SYMBOL(fsapi_dfr_update_fat_next); + +void fsapi_dfr_check_discard(struct super_block *sb) +{ + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + defrag_check_discard(sb); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); +} +EXPORT_SYMBOL(fsapi_dfr_check_discard); + +void fsapi_dfr_free_clus(struct super_block *sb, u32 clus) +{ + mutex_lock(&(SDFAT_SB(sb)->s_vlock)); + defrag_free_cluster(sb, clus); + mutex_unlock(&(SDFAT_SB(sb)->s_vlock)); +} +EXPORT_SYMBOL(fsapi_dfr_free_clus); + +s32 fsapi_dfr_check_dfr_required(struct super_block *sb, int *totalau, int *cleanau, int *fullau) +{ + /* volume lock is not required */ + return defrag_check_defrag_required(sb, totalau, cleanau, fullau); +} +EXPORT_SYMBOL(fsapi_dfr_check_dfr_required); + +s32 fsapi_dfr_check_dfr_on(struct inode *inode, loff_t start, loff_t end, s32 cancel, const char *caller) +{ + /* volume lock is not required */ + return defrag_check_defrag_on(inode, start, end, cancel, caller); +} +EXPORT_SYMBOL(fsapi_dfr_check_dfr_on); + + + +#ifdef CONFIG_SDFAT_DFR_DEBUG +void fsapi_dfr_spo_test(struct super_block *sb, int flag, const char *caller) +{ + /* volume lock is not required */ + defrag_spo_test(sb, flag, caller); +} +EXPORT_SYMBOL(fsapi_dfr_spo_test); +#endif + + +#endif /* CONFIG_SDFAT_DFR */ + +/* end of sdfat_api.c */ diff --git a/fs/sdfat/api.h b/fs/sdfat/api.h new file mode 100644 index 0000000000000..0bd05d700c85e --- /dev/null +++ b/fs/sdfat/api.h @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _SDFAT_API_H +#define _SDFAT_API_H + +#include "config.h" +#include "sdfat_fs.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*----------------------------------------------------------------------*/ +/* Configure Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ +/* cache size (in number of sectors) */ +/* (should be an exponential value of 2) */ +#define FAT_CACHE_SIZE 128 +#define FAT_CACHE_HASH_SIZE 64 +#define BUF_CACHE_SIZE 256 +#define BUF_CACHE_HASH_SIZE 64 + +/* Read-ahead related */ +/* First config vars. should be pow of 2 */ +#define FCACHE_MAX_RA_SIZE (PAGE_SIZE) +#define DCACHE_MAX_RA_SIZE (128*1024) + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ +/* type values */ +#define TYPE_UNUSED 0x0000 +#define TYPE_DELETED 0x0001 +#define TYPE_INVALID 0x0002 +#define TYPE_CRITICAL_PRI 0x0100 +#define TYPE_BITMAP 0x0101 +#define TYPE_UPCASE 0x0102 +#define TYPE_VOLUME 0x0103 +#define TYPE_DIR 0x0104 +#define TYPE_FILE 0x011F +#define TYPE_SYMLINK 0x015F +#define TYPE_CRITICAL_SEC 0x0200 +#define TYPE_STREAM 0x0201 +#define TYPE_EXTEND 0x0202 +#define TYPE_ACL 0x0203 +#define TYPE_BENIGN_PRI 0x0400 +#define TYPE_GUID 0x0401 +#define TYPE_PADDING 0x0402 +#define TYPE_ACLTAB 0x0403 +#define TYPE_BENIGN_SEC 0x0800 +#define TYPE_ALL 0x0FFF + +/* eio values */ +#define SDFAT_EIO_NONE (0x00000000) +#define SDFAT_EIO_READ (0x00000001) +#define SDFAT_EIO_WRITE (0x00000002) +#define SDFAT_EIO_BDI (0x00000004) + +/* modes for volume allocation unit status */ +#define VOL_AU_STAT_TOTAL (0) +#define VOL_AU_STAT_CLEAN (1) +#define VOL_AU_STAT_FULL (2) + +/*----------------------------------------------------------------------*/ +/* NLS Type Definitions */ +/*----------------------------------------------------------------------*/ + +/* DOS name structure */ +typedef struct { + u8 name[DOS_NAME_LENGTH]; + u8 name_case; +} DOS_NAME_T; + +/* unicode name structure */ +typedef struct { + u16 name[MAX_NAME_LENGTH+3]; /* +3 for null and for converting */ + u16 name_hash; + u8 name_len; +} UNI_NAME_T; + +/*----------------------------------------------------------------------*/ +/* Type Definitions */ +/*----------------------------------------------------------------------*/ +/* should be merged it to DATE_TIME_T */ +typedef struct { + u16 sec; /* 0 ~ 59 */ + u16 min; /* 0 ~ 59 */ + u16 hour; /* 0 ~ 23 */ + u16 day; /* 1 ~ 31 */ + u16 mon; /* 1 ~ 12 */ + u16 year; /* 0 ~ 127 (since 1980) */ +} TIMESTAMP_T; + + +typedef struct { + u16 Year; + u16 Month; + u16 Day; + u16 Hour; + u16 Minute; + u16 Second; + u16 MilliSecond; +} DATE_TIME_T; + +typedef struct { + u64 Offset; // start sector number of the partition + u64 Size; // in sectors +} PART_INFO_T; + +typedef struct { + u32 SecSize; // sector size in bytes + u64 DevSize; // block device size in sectors +} DEV_INFO_T; + +typedef struct { + u32 FatType; + u32 ClusterSize; + u32 NumClusters; + u32 FreeClusters; + u32 UsedClusters; +} VOL_INFO_T; + +/* directory structure */ +typedef struct { + u32 dir; + u32 size; + u8 flags; +} CHAIN_T; + +/* hint structure */ +typedef struct { + u32 clu; + union { + u32 off; // cluster offset + s32 eidx; // entry index + }; +} HINT_T; + +typedef struct { + spinlock_t cache_lru_lock; + struct list_head cache_lru; + s32 nr_caches; + u32 cache_valid_id; // for avoiding the race between alloc and free +} EXTENT_T; + +/* first empty entry hint information */ +typedef struct { + s32 eidx; // entry index of a directory + s32 count; // count of continuous empty entry + CHAIN_T cur; // the cluster that first empty slot exists in +} HINT_FEMP_T; + +/* file id structure */ +typedef struct { + CHAIN_T dir; + s32 entry; + u32 type; + u32 attr; + u32 start_clu; + u64 size; + u8 flags; + u8 reserved[3]; // padding + u32 version; // the copy of low 32bit of i_version to check the validation of hint_stat + s64 rwoffset; // file offset or dentry index for readdir + EXTENT_T extent; // extent cache for a file + HINT_T hint_bmap; // hint for cluster last accessed + HINT_T hint_stat; // hint for entry index we try to lookup next time + HINT_FEMP_T hint_femp; // hint for first empty entry +} FILE_ID_T; + +typedef struct { + s8 *lfn; + s8 *sfn; + s32 lfnbuf_len; //usally MAX_UNINAME_BUF_SIZE + s32 sfnbuf_len; //usally MAX_DOSNAME_BUF_SIZE, used only for vfat, not for exfat +} DENTRY_NAMEBUF_T; + +typedef struct { + u32 Attr; + u64 Size; + u32 NumSubdirs; + DATE_TIME_T CreateTimestamp; + DATE_TIME_T ModifyTimestamp; + DATE_TIME_T AccessTimestamp; + DENTRY_NAMEBUF_T NameBuf; +} DIR_ENTRY_T; + +/* cache information */ +typedef struct __cache_entry { + struct __cache_entry *next; + struct __cache_entry *prev; + struct { + struct __cache_entry *next; + struct __cache_entry *prev; + } hash; + u64 sec; + u32 flag; + struct buffer_head *bh; +} cache_ent_t; + +/*----------------------------------------------------------------------*/ +/* Type Definitions : Wrapper & In-Core */ +/*----------------------------------------------------------------------*/ +typedef struct __FATENT_OPS_T { + s32 (*ent_get)(struct super_block *sb, u32 loc, u32 *content); + s32 (*ent_set)(struct super_block *sb, u32 loc, u32 content); +} FATENT_OPS_T; + +typedef struct { + s32 (*alloc_cluster)(struct super_block *, u32, CHAIN_T *, s32); + s32 (*free_cluster)(struct super_block *, CHAIN_T *, s32); + s32 (*count_used_clusters)(struct super_block *, u32 *); + s32 (*init_dir_entry)(struct super_block *, CHAIN_T *, s32, u32, u32, u64); + s32 (*init_ext_entry)(struct super_block *, CHAIN_T *, s32, s32, UNI_NAME_T *, DOS_NAME_T *); + s32 (*find_dir_entry)(struct super_block *, FILE_ID_T *, CHAIN_T *, UNI_NAME_T *, s32, DOS_NAME_T *, u32); + s32 (*delete_dir_entry)(struct super_block *, CHAIN_T *, s32, s32, s32); + void (*get_uniname_from_ext_entry)(struct super_block *, CHAIN_T *, s32, u16 *); + s32 (*count_ext_entries)(struct super_block *, CHAIN_T *, s32, DENTRY_T *); + s32 (*calc_num_entries)(UNI_NAME_T *); + s32 (*check_max_dentries)(FILE_ID_T *); + u32 (*get_entry_type)(DENTRY_T *); + void (*set_entry_type)(DENTRY_T *, u32); + u32 (*get_entry_attr)(DENTRY_T *); + void (*set_entry_attr)(DENTRY_T *, u32); + u8 (*get_entry_flag)(DENTRY_T *); + void (*set_entry_flag)(DENTRY_T *, u8); + u32 (*get_entry_clu0)(DENTRY_T *); + void (*set_entry_clu0)(DENTRY_T *, u32); + u64 (*get_entry_size)(DENTRY_T *); + void (*set_entry_size)(DENTRY_T *, u64); + void (*get_entry_time)(DENTRY_T *, TIMESTAMP_T *, u8); + void (*set_entry_time)(DENTRY_T *, TIMESTAMP_T *, u8); + u32 (*get_au_stat)(struct super_block *, s32); +} FS_FUNC_T; + +typedef struct __FS_INFO_T { + s32 bd_opened; // opened or not + u32 vol_type; // volume FAT type + u32 vol_id; // volume serial number + u64 num_sectors; // num of sectors in volume + u32 num_clusters; // num of clusters in volume + u32 cluster_size; // cluster size in bytes + u32 cluster_size_bits; + u32 sect_per_clus; // cluster size in sectors + u32 sect_per_clus_bits; + u64 FAT1_start_sector; // FAT1 start sector + u64 FAT2_start_sector; // FAT2 start sector + u64 root_start_sector; // root dir start sector + u64 data_start_sector; // data area start sector + u32 num_FAT_sectors; // num of FAT sectors + u32 root_dir; // root dir cluster + u32 dentries_in_root; // num of dentries in root dir + u32 dentries_per_clu; // num of dentries per cluster + u32 vol_flag; // volume dirty flag + struct buffer_head *pbr_bh; // buffer_head of PBR sector + + u32 map_clu; // allocation bitmap start cluster + u32 map_sectors; // num of allocation bitmap sectors + struct buffer_head **vol_amap; // allocation bitmap + + u16 **vol_utbl; // upcase table + + u32 clu_srch_ptr; // cluster search pointer + u32 used_clusters; // number of used clusters + + u32 prev_eio; // block device operation error flag + + FS_FUNC_T *fs_func; + FATENT_OPS_T *fatent_ops; + + s32 reserved_clusters; // # of reserved clusters (DA) + void *amap; // AU Allocation Map + + /* fat cache */ + struct { + cache_ent_t pool[FAT_CACHE_SIZE]; + cache_ent_t lru_list; + cache_ent_t hash_list[FAT_CACHE_HASH_SIZE]; + } fcache; + + /* meta cache */ + struct { + cache_ent_t pool[BUF_CACHE_SIZE]; + cache_ent_t lru_list; + cache_ent_t keep_list; // CACHEs in this list will not be kicked by normal lru operations + cache_ent_t hash_list[BUF_CACHE_HASH_SIZE]; + } dcache; +} FS_INFO_T; + +/*======================================================================*/ +/* */ +/* API FUNCTION DECLARATIONS */ +/* (CHANGE THIS PART IF REQUIRED) */ +/* */ +/*======================================================================*/ + +/*----------------------------------------------------------------------*/ +/* External Function Declarations */ +/*----------------------------------------------------------------------*/ + +/* file system initialization & shutdown functions */ +s32 fsapi_init(void); +s32 fsapi_shutdown(void); + +/* volume management functions */ +s32 fsapi_mount(struct super_block *sb); +s32 fsapi_umount(struct super_block *sb); +s32 fsapi_statfs(struct super_block *sb, VOL_INFO_T *info); +s32 fsapi_sync_fs(struct super_block *sb, s32 do_sync); +s32 fsapi_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync); + +/* file management functions */ +s32 fsapi_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid); +s32 fsapi_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid); +s32 fsapi_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount); +s32 fsapi_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount); +s32 fsapi_remove(struct inode *inode, FILE_ID_T *fid); /* unlink and truncate */ +s32 fsapi_truncate(struct inode *inode, u64 old_size, u64 new_size); +s32 fsapi_rename(struct inode *old_parent_inode, FILE_ID_T *fid, + struct inode *new_parent_inode, struct dentry *new_dentry); +s32 fsapi_unlink(struct inode *inode, FILE_ID_T *fid); +s32 fsapi_read_inode(struct inode *inode, DIR_ENTRY_T *info); +s32 fsapi_write_inode(struct inode *inode, DIR_ENTRY_T *info, int sync); +s32 fsapi_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest); +s32 fsapi_reserve_clus(struct inode *inode); + +/* directory management functions */ +s32 fsapi_mkdir(struct inode *inode, u8 *path, FILE_ID_T *fid); +s32 fsapi_readdir(struct inode *inode, DIR_ENTRY_T *dir_entry); +s32 fsapi_rmdir(struct inode *inode, FILE_ID_T *fid); + +/* FAT & buf cache functions */ +s32 fsapi_cache_flush(struct super_block *sb, int do_sync); +s32 fsapi_cache_release(struct super_block *sb); + +/* extra info functions */ +u32 fsapi_get_au_stat(struct super_block *sb, s32 mode); + +/* extent cache functions */ +void fsapi_invalidate_extent(struct inode *inode); + +#ifdef CONFIG_SDFAT_DFR +/*----------------------------------------------------------------------*/ +/* Defragmentation related */ +/*----------------------------------------------------------------------*/ + +s32 fsapi_dfr_get_info(struct super_block *sb, void *arg); + +s32 fsapi_dfr_scan_dir(struct super_block *sb, void *args); + +s32 fsapi_dfr_validate_clus(struct inode *inode, void *chunk, int skip_prev); +s32 fsapi_dfr_reserve_clus(struct super_block *sb, s32 nr_clus); +s32 fsapi_dfr_mark_ignore(struct super_block *sb, unsigned int clus); +void fsapi_dfr_unmark_ignore_all(struct super_block *sb); + +s32 fsapi_dfr_map_clus(struct inode *inode, u32 clu_offset, u32 *clu); +void fsapi_dfr_writepage_endio(struct page *page); + +void fsapi_dfr_update_fat_prev(struct super_block *sb, int force); +void fsapi_dfr_update_fat_next(struct super_block *sb); +void fsapi_dfr_check_discard(struct super_block *sb); +void fsapi_dfr_free_clus(struct super_block *sb, u32 clus); + +s32 fsapi_dfr_check_dfr_required(struct super_block *sb, int *totalau, int *cleanau, int *fullau); +s32 fsapi_dfr_check_dfr_on(struct inode *inode, loff_t start, loff_t end, s32 cancel, const char *caller); + + +#ifdef CONFIG_SDFAT_DFR_DEBUG +void fsapi_dfr_spo_test(struct super_block *sb, int flag, const char *caller); +#endif /* CONFIG_SDFAT_DFR_DEBUG */ + +#endif /* CONFIG_SDFAT_DFR */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _SDFAT_API_H */ + +/* end of api.h */ diff --git a/fs/sdfat/blkdev.c b/fs/sdfat/blkdev.c new file mode 100644 index 0000000000000..264c670df0f0d --- /dev/null +++ b/fs/sdfat/blkdev.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : blkdev.c */ +/* PURPOSE : sdFAT Block Device Driver Glue Layer */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/************************************************************************/ + +#include +#include +#include + +#include "sdfat.h" + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Local Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY */ +/************************************************************************/ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + /* EMPTY */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) */ +static struct backing_dev_info *inode_to_bdi(struct inode *bd_inode) +{ + return bd_inode->i_mapping->backing_dev_info; +} +#endif + +/*======================================================================*/ +/* Function Definitions */ +/*======================================================================*/ +s32 bdev_open_dev(struct super_block *sb) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (fsi->bd_opened) + return 0; + + fsi->bd_opened = true; + return 0; +} + +s32 bdev_close_dev(struct super_block *sb) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + fsi->bd_opened = false; + return 0; +} + +static inline s32 block_device_ejected(struct super_block *sb) +{ + struct inode *bd_inode = sb->s_bdev->bd_inode; + struct backing_dev_info *bdi = inode_to_bdi(bd_inode); + + return (bdi->dev == NULL); +} + +s32 bdev_check_bdi_valid(struct super_block *sb) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (block_device_ejected(sb)) { + if (!(fsi->prev_eio & SDFAT_EIO_BDI)) { + fsi->prev_eio |= SDFAT_EIO_BDI; + sdfat_log_msg(sb, KERN_ERR, "%s: block device is " + "eliminated.(bdi:%p)", __func__, sb->s_bdi); + sdfat_debug_warn_on(1); + } + return -ENXIO; + } + + return 0; +} + + +/* Make a readahead request */ +s32 bdev_readahead(struct super_block *sb, u64 secno, u64 num_secs) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u32 sects_per_page = (PAGE_SIZE >> sb->s_blocksize_bits); + struct blk_plug plug; + u64 i; + + if (!fsi->bd_opened) + return -EIO; + + blk_start_plug(&plug); + for (i = 0; i < num_secs; i++) { + if (i && !(i & (sects_per_page - 1))) + blk_flush_plug(current); + sb_breadahead(sb, (sector_t)(secno + i)); + } + blk_finish_plug(&plug); + + return 0; +} + +s32 bdev_mread(struct super_block *sb, u64 secno, struct buffer_head **bh, u64 num_secs, s32 read) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u8 blksize_bits = sb->s_blocksize_bits; +#ifdef CONFIG_SDFAT_DBG_IOCTL + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + long flags = sbi->debug_flags; + + if (flags & SDFAT_DEBUGFLAGS_ERROR_RW) + return -EIO; +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + + if (!fsi->bd_opened) + return -EIO; + + brelse(*bh); + + if (read) + *bh = __bread(sb->s_bdev, (sector_t)secno, num_secs << blksize_bits); + else + *bh = __getblk(sb->s_bdev, (sector_t)secno, num_secs << blksize_bits); + + /* read successfully */ + if (*bh) + return 0; + + /* + * patch 1.2.4 : reset ONCE warning message per volume. + */ + if (!(fsi->prev_eio & SDFAT_EIO_READ)) { + fsi->prev_eio |= SDFAT_EIO_READ; + sdfat_log_msg(sb, KERN_ERR, "%s: No bh. I/O error.", __func__); + sdfat_debug_warn_on(1); + } + + return -EIO; +} + +s32 bdev_mwrite(struct super_block *sb, u64 secno, struct buffer_head *bh, u64 num_secs, s32 sync) +{ + u64 count; + struct buffer_head *bh2; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); +#ifdef CONFIG_SDFAT_DBG_IOCTL + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + long flags = sbi->debug_flags; + + if (flags & SDFAT_DEBUGFLAGS_ERROR_RW) + return -EIO; +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + + if (!fsi->bd_opened) + return -EIO; + + if (secno == bh->b_blocknr) { + set_buffer_uptodate(bh); + mark_buffer_dirty(bh); + if (sync && (sync_dirty_buffer(bh) != 0)) + return -EIO; + } else { + count = num_secs << sb->s_blocksize_bits; + + bh2 = __getblk(sb->s_bdev, (sector_t)secno, count); + + if (!bh2) + goto no_bh; + + lock_buffer(bh2); + memcpy(bh2->b_data, bh->b_data, count); + set_buffer_uptodate(bh2); + mark_buffer_dirty(bh2); + unlock_buffer(bh2); + if (sync && (sync_dirty_buffer(bh2) != 0)) { + __brelse(bh2); + goto no_bh; + } + __brelse(bh2); + } + return 0; +no_bh: + /* + * patch 1.2.4 : reset ONCE warning message per volume. + */ + if (!(fsi->prev_eio & SDFAT_EIO_WRITE)) { + fsi->prev_eio |= SDFAT_EIO_WRITE; + sdfat_log_msg(sb, KERN_ERR, "%s: No bh. I/O error.", __func__); + sdfat_debug_warn_on(1); + } + + return -EIO; +} + +s32 bdev_sync_all(struct super_block *sb) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); +#ifdef CONFIG_SDFAT_DBG_IOCTL + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + long flags = sbi->debug_flags; + + if (flags & SDFAT_DEBUGFLAGS_ERROR_RW) + return -EIO; +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + + if (!fsi->bd_opened) + return -EIO; + + return sync_blockdev(sb->s_bdev); +} + +/* + * Sector Read/Write Functions + */ +s32 read_sect(struct super_block *sb, u64 sec, struct buffer_head **bh, s32 read) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + BUG_ON(!bh); + if ((sec >= fsi->num_sectors) && (fsi->num_sectors > 0)) { + sdfat_fs_error_ratelimit(sb, + "%s: out of range (sect:%llu)", __func__, sec); + return -EIO; + } + + if (bdev_mread(sb, sec, bh, 1, read)) { + sdfat_fs_error_ratelimit(sb, + "%s: I/O error (sect:%llu)", __func__, sec); + return -EIO; + } + + return 0; +} + +s32 write_sect(struct super_block *sb, u64 sec, struct buffer_head *bh, s32 sync) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + BUG_ON(!bh); + if ((sec >= fsi->num_sectors) && (fsi->num_sectors > 0)) { + sdfat_fs_error_ratelimit(sb, + "%s: out of range (sect:%llu)", __func__, sec); + return -EIO; + } + + if (bdev_mwrite(sb, sec, bh, 1, sync)) { + sdfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu)", + __func__, sec); + return -EIO; + } + + return 0; +} + +s32 read_msect(struct super_block *sb, u64 sec, struct buffer_head **bh, u64 num_secs, s32 read) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + BUG_ON(!bh); + if (((sec+num_secs) > fsi->num_sectors) && (fsi->num_sectors > 0)) { + sdfat_fs_error_ratelimit(sb, "%s: out of range(sect:%llu len:%llu)", + __func__, sec, num_secs); + return -EIO; + } + + if (bdev_mread(sb, sec, bh, num_secs, read)) { + sdfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu len:%llu)", + __func__, sec, num_secs); + return -EIO; + } + + return 0; +} + +s32 write_msect(struct super_block *sb, u64 sec, struct buffer_head *bh, u64 num_secs, s32 sync) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + BUG_ON(!bh); + if (((sec+num_secs) > fsi->num_sectors) && (fsi->num_sectors > 0)) { + sdfat_fs_error_ratelimit(sb, "%s: out of range(sect:%llu len:%llu)", + __func__, sec, num_secs); + return -EIO; + } + + + if (bdev_mwrite(sb, sec, bh, num_secs, sync)) { + sdfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu len:%llu)", + __func__, sec, num_secs); + return -EIO; + } + + return 0; +} + +static inline void __blkdev_write_bhs(struct buffer_head **bhs, s32 nr_bhs) +{ + s32 i; + + for (i = 0; i < nr_bhs; i++) + write_dirty_buffer(bhs[i], WRITE); +} + +static inline s32 __blkdev_sync_bhs(struct buffer_head **bhs, s32 nr_bhs) +{ + s32 i, err = 0; + + for (i = 0; i < nr_bhs; i++) { + wait_on_buffer(bhs[i]); + if (!err && !buffer_uptodate(bhs[i])) + err = -EIO; + } + return err; +} + +static inline s32 __buffer_zeroed(struct super_block *sb, u64 blknr, u64 num_secs) +{ + struct buffer_head *bhs[MAX_BUF_PER_PAGE]; + s32 nr_bhs = MAX_BUF_PER_PAGE; + u64 last_blknr = blknr + num_secs; + s32 err, i, n; + struct blk_plug plug; + + /* Zeroing the unused blocks on this cluster */ + n = 0; + blk_start_plug(&plug); + while (blknr < last_blknr) { + bhs[n] = sb_getblk(sb, (sector_t)blknr); + if (!bhs[n]) { + err = -ENOMEM; + blk_finish_plug(&plug); + goto error; + } + memset(bhs[n]->b_data, 0, sb->s_blocksize); + set_buffer_uptodate(bhs[n]); + mark_buffer_dirty(bhs[n]); + + n++; + blknr++; + + if (blknr == last_blknr) + break; + + if (n == nr_bhs) { + __blkdev_write_bhs(bhs, n); + + for (i = 0; i < n; i++) + brelse(bhs[i]); + n = 0; + } + } + __blkdev_write_bhs(bhs, n); + blk_finish_plug(&plug); + + err = __blkdev_sync_bhs(bhs, n); + if (err) + goto error; + + for (i = 0; i < n; i++) + brelse(bhs[i]); + + return 0; + +error: + EMSG("%s: failed zeroed sect %llu\n", __func__, blknr); + for (i = 0; i < n; i++) + bforget(bhs[i]); + + return err; +} + +s32 write_msect_zero(struct super_block *sb, u64 sec, u64 num_secs) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (((sec+num_secs) > fsi->num_sectors) && (fsi->num_sectors > 0)) { + sdfat_fs_error_ratelimit(sb, "%s: out of range(sect:%llu len:%llu)", + __func__, sec, num_secs); + return -EIO; + } + + /* Just return -EAGAIN if it is failed */ + if (__buffer_zeroed(sb, sec, num_secs)) + return -EAGAIN; + + return 0; +} /* end of write_msect_zero */ + +/* end of blkdev.c */ diff --git a/fs/sdfat/cache.c b/fs/sdfat/cache.c new file mode 100644 index 0000000000000..f0170c5602075 --- /dev/null +++ b/fs/sdfat/cache.c @@ -0,0 +1,842 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : cache.c */ +/* PURPOSE : sdFAT Cache Manager */ +/* (FAT Cache & Buffer Cache) */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include /* for mark_page_accessed() */ +#include + +#include "sdfat.h" +#include "core.h" + +#define DEBUG_HASH_LIST +#define DEBUG_HASH_PREV (0xAAAA5555) +#define DEBUG_HASH_NEXT (0x5555AAAA) + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ +/* All buffer structures are protected w/ fsi->v_sem */ + +/*----------------------------------------------------------------------*/ +/* Local Variable Definitions */ +/*----------------------------------------------------------------------*/ +#define LOCKBIT (0x01) +#define DIRTYBIT (0x02) +#define KEEPBIT (0x04) + +/*----------------------------------------------------------------------*/ +/* Cache handling function declarations */ +/*----------------------------------------------------------------------*/ +static cache_ent_t *__fcache_find(struct super_block *sb, u64 sec); +static cache_ent_t *__fcache_get(struct super_block *sb); +static void __fcache_insert_hash(struct super_block *sb, cache_ent_t *bp); +static void __fcache_remove_hash(cache_ent_t *bp); + +static cache_ent_t *__dcache_find(struct super_block *sb, u64 sec); +static cache_ent_t *__dcache_get(struct super_block *sb); +static void __dcache_insert_hash(struct super_block *sb, cache_ent_t *bp); +static void __dcache_remove_hash(cache_ent_t *bp); + +/*----------------------------------------------------------------------*/ +/* Static functions */ +/*----------------------------------------------------------------------*/ +static void push_to_mru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->next = list->next; + bp->prev = list; + list->next->prev = bp; + list->next = bp; +} + +static void push_to_lru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->prev = list->prev; + bp->next = list; + list->prev->next = bp; + list->prev = bp; +} + +static void move_to_mru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->prev->next = bp->next; + bp->next->prev = bp->prev; + push_to_mru(bp, list); +} + +static void move_to_lru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->prev->next = bp->next; + bp->next->prev = bp->prev; + push_to_lru(bp, list); +} + +static inline s32 __check_hash_valid(cache_ent_t *bp) +{ +#ifdef DEBUG_HASH_LIST + if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) || + (bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) { + return -EINVAL; + } +#endif + if ((bp->hash.next == bp) || (bp->hash.prev == bp)) + return -EINVAL; + + return 0; +} + +static inline void __remove_from_hash(cache_ent_t *bp) +{ + (bp->hash.prev)->hash.next = bp->hash.next; + (bp->hash.next)->hash.prev = bp->hash.prev; + bp->hash.next = bp; + bp->hash.prev = bp; +#ifdef DEBUG_HASH_LIST + bp->hash.next = (cache_ent_t *)DEBUG_HASH_NEXT; + bp->hash.prev = (cache_ent_t *)DEBUG_HASH_PREV; +#endif +} + +/* Do FAT mirroring (don't sync) + * sec: sector No. in FAT1 + * bh: bh of sec. + */ +static inline s32 __fat_copy(struct super_block *sb, u64 sec, struct buffer_head *bh, int sync) +{ +#ifdef CONFIG_SDFAT_FAT_MIRRORING + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u64 sec2; + + if (fsi->FAT2_start_sector != fsi->FAT1_start_sector) { + sec2 = sec - fsi->FAT1_start_sector + fsi->FAT2_start_sector; + BUG_ON(sec2 != (sec + (u64)fsi->num_FAT_sectors)); + + MMSG("BD: fat mirroring (%llu in FAT1, %llu in FAT2)\n", sec, sec2); + if (write_sect(sb, sec2, bh, sync)) + return -EIO; + } +#else + /* DO NOTHING */ +#endif + return 0; +} /* end of __fat_copy */ + +/* + * returns 1, if bp is flushed + * returns 0, if bp is not dirty + * returns -1, if error occurs + */ +static s32 __fcache_ent_flush(struct super_block *sb, cache_ent_t *bp, u32 sync) +{ + if (!(bp->flag & DIRTYBIT)) + return 0; +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + // Make buffer dirty (XXX: Naive impl.) + if (write_sect(sb, bp->sec, bp->bh, 0)) + return -EIO; + + if (__fat_copy(sb, bp->sec, bp->bh, 0)) + return -EIO; +#endif + bp->flag &= ~(DIRTYBIT); + + if (sync) + sync_dirty_buffer(bp->bh); + + return 1; +} + +static s32 __fcache_ent_discard(struct super_block *sb, cache_ent_t *bp) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + __fcache_remove_hash(bp); + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + move_to_lru(bp, &fsi->fcache.lru_list); + return 0; +} + +u8 *fcache_getblk(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u32 page_ra_count = FCACHE_MAX_RA_SIZE >> sb->s_blocksize_bits; + + bp = __fcache_find(sb, sec); + if (bp) { + if (bdev_check_bdi_valid(sb)) { + __fcache_ent_flush(sb, bp, 0); + __fcache_ent_discard(sb, bp); + return NULL; + } + move_to_mru(bp, &fsi->fcache.lru_list); + return bp->bh->b_data; + } + + bp = __fcache_get(sb); + if (!__check_hash_valid(bp)) + __fcache_remove_hash(bp); + + bp->sec = sec; + bp->flag = 0; + __fcache_insert_hash(sb, bp); + + /* Naive FAT read-ahead (increase I/O unit to page_ra_count) */ + if ((sec & (page_ra_count - 1)) == 0) + bdev_readahead(sb, sec, (u64)page_ra_count); + + /* + * patch 1.2.4 : buffer_head null pointer exception problem. + * + * When read_sect is failed, fcache should be moved to + * EMPTY hash_list and the first of lru_list. + */ + if (read_sect(sb, sec, &(bp->bh), 1)) { + __fcache_ent_discard(sb, bp); + return NULL; + } + + return bp->bh->b_data; +} + +static inline int __mark_delayed_dirty(struct super_block *sb, cache_ent_t *bp) +{ +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (fsi->vol_type == EXFAT) + return -ENOTSUPP; + + bp->flag |= DIRTYBIT; + return 0; +#else + return -ENOTSUPP; +#endif +} + + + +s32 fcache_modify(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + + bp = __fcache_find(sb, sec); + if (!bp) { + sdfat_fs_error(sb, "Can`t find fcache (sec 0x%016llx)", sec); + return -EIO; + } + + if (!__mark_delayed_dirty(sb, bp)) + return 0; + + if (write_sect(sb, sec, bp->bh, 0)) + return -EIO; + + if (__fat_copy(sb, sec, bp->bh, 0)) + return -EIO; + + return 0; +} + +/*======================================================================*/ +/* Cache Initialization Functions */ +/*======================================================================*/ +s32 meta_cache_init(struct super_block *sb) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 i; + + /* LRU list */ + fsi->fcache.lru_list.next = &fsi->fcache.lru_list; + fsi->fcache.lru_list.prev = fsi->fcache.lru_list.next; + + for (i = 0; i < FAT_CACHE_SIZE; i++) { + fsi->fcache.pool[i].sec = ~0; + fsi->fcache.pool[i].flag = 0; + fsi->fcache.pool[i].bh = NULL; + fsi->fcache.pool[i].prev = NULL; + fsi->fcache.pool[i].next = NULL; + push_to_mru(&(fsi->fcache.pool[i]), &fsi->fcache.lru_list); + } + + fsi->dcache.lru_list.next = &fsi->dcache.lru_list; + fsi->dcache.lru_list.prev = fsi->dcache.lru_list.next; + fsi->dcache.keep_list.next = &fsi->dcache.keep_list; + fsi->dcache.keep_list.prev = fsi->dcache.keep_list.next; + + // Initially, all the BUF_CACHEs are in the LRU list + for (i = 0; i < BUF_CACHE_SIZE; i++) { + fsi->dcache.pool[i].sec = ~0; + fsi->dcache.pool[i].flag = 0; + fsi->dcache.pool[i].bh = NULL; + fsi->dcache.pool[i].prev = NULL; + fsi->dcache.pool[i].next = NULL; + push_to_mru(&(fsi->dcache.pool[i]), &fsi->dcache.lru_list); + } + + /* HASH list */ + for (i = 0; i < FAT_CACHE_HASH_SIZE; i++) { + fsi->fcache.hash_list[i].sec = ~0; + fsi->fcache.hash_list[i].hash.next = &(fsi->fcache.hash_list[i]); +; + fsi->fcache.hash_list[i].hash.prev = fsi->fcache.hash_list[i].hash.next; + } + + for (i = 0; i < FAT_CACHE_SIZE; i++) + __fcache_insert_hash(sb, &(fsi->fcache.pool[i])); + + for (i = 0; i < BUF_CACHE_HASH_SIZE; i++) { + fsi->dcache.hash_list[i].sec = ~0; + fsi->dcache.hash_list[i].hash.next = &(fsi->dcache.hash_list[i]); + + fsi->dcache.hash_list[i].hash.prev = fsi->dcache.hash_list[i].hash.next; + } + + for (i = 0; i < BUF_CACHE_SIZE; i++) + __dcache_insert_hash(sb, &(fsi->dcache.pool[i])); + + return 0; +} + +s32 meta_cache_shutdown(struct super_block *sb) +{ + return 0; +} + +/*======================================================================*/ +/* FAT Read/Write Functions */ +/*======================================================================*/ +s32 fcache_release_all(struct super_block *sb) +{ + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 dirtycnt = 0; + + bp = fsi->fcache.lru_list.next; + while (bp != &fsi->fcache.lru_list) { + s32 ret_tmp = __fcache_ent_flush(sb, bp, 0); + + if (ret_tmp < 0) + ret = ret_tmp; + else + dirtycnt += ret_tmp; + + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + bp = bp->next; + } + + DMSG("BD:Release / dirty fat cache: %d (err:%d)\n", dirtycnt, ret); + return ret; +} + + +/* internal DIRTYBIT marked => bh dirty */ +s32 fcache_flush(struct super_block *sb, u32 sync) +{ + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 dirtycnt = 0; + + bp = fsi->fcache.lru_list.next; + while (bp != &fsi->fcache.lru_list) { + ret = __fcache_ent_flush(sb, bp, sync); + if (ret < 0) + break; + + dirtycnt += ret; + bp = bp->next; + } + + MMSG("BD: flush / dirty fat cache: %d (err:%d)\n", dirtycnt, ret); + return ret; +} + +static cache_ent_t *__fcache_find(struct super_block *sb, u64 sec) +{ + s32 off; + cache_ent_t *bp, *hp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + off = (sec + (sec >> fsi->sect_per_clus_bits)) & (FAT_CACHE_HASH_SIZE - 1); + hp = &(fsi->fcache.hash_list[off]); + for (bp = hp->hash.next; bp != hp; bp = bp->hash.next) { + if (bp->sec == sec) { + /* + * patch 1.2.4 : for debugging + */ + WARN(!bp->bh, "[SDFAT] fcache has no bh. " + "It will make system panic.\n"); + + touch_buffer(bp->bh); + return bp; + } + } + return NULL; +} + +static cache_ent_t *__fcache_get(struct super_block *sb) +{ + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + bp = fsi->fcache.lru_list.prev; +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + while (bp->flag & DIRTYBIT) { + cache_ent_t *bp_prev = bp->prev; + + bp = bp_prev; + if (bp == &fsi->fcache.lru_list) { + DMSG("BD: fat cache flooding\n"); + fcache_flush(sb, 0); // flush all dirty FAT caches + bp = fsi->fcache.lru_list.prev; + } + } +#endif +// if (bp->flag & DIRTYBIT) +// sync_dirty_buffer(bp->bh); + + move_to_mru(bp, &fsi->fcache.lru_list); + return bp; +} + +static void __fcache_insert_hash(struct super_block *sb, cache_ent_t *bp) +{ + s32 off; + cache_ent_t *hp; + FS_INFO_T *fsi; + + fsi = &(SDFAT_SB(sb)->fsi); + off = (bp->sec + (bp->sec >> fsi->sect_per_clus_bits)) & (FAT_CACHE_HASH_SIZE-1); + + hp = &(fsi->fcache.hash_list[off]); + bp->hash.next = hp->hash.next; + bp->hash.prev = hp; + hp->hash.next->hash.prev = bp; + hp->hash.next = bp; +} + + +static void __fcache_remove_hash(cache_ent_t *bp) +{ +#ifdef DEBUG_HASH_LIST + if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) || + (bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) { + EMSG("%s: FATAL: tried to remove already-removed-cache-entry" + "(bp:%p)\n", __func__, bp); + return; + } +#endif + WARN_ON(bp->flag & DIRTYBIT); + __remove_from_hash(bp); +} + +/*======================================================================*/ +/* Buffer Read/Write Functions */ +/*======================================================================*/ +/* Read-ahead a cluster */ +s32 dcache_readahead(struct super_block *sb, u64 sec) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + struct buffer_head *bh; + u32 max_ra_count = DCACHE_MAX_RA_SIZE >> sb->s_blocksize_bits; + u32 page_ra_count = PAGE_SIZE >> sb->s_blocksize_bits; + u32 adj_ra_count = max(fsi->sect_per_clus, page_ra_count); + u32 ra_count = min(adj_ra_count, max_ra_count); + + /* Read-ahead is not required */ + if (fsi->sect_per_clus == 1) + return 0; + + if (sec < fsi->data_start_sector) { + EMSG("BD: %s: requested sector is invalid(sect:%llu, root:%llu)\n", + __func__, sec, fsi->data_start_sector); + return -EIO; + } + + /* Not sector aligned with ra_count, resize ra_count to page size */ + if ((sec - fsi->data_start_sector) & (ra_count - 1)) + ra_count = page_ra_count; + + bh = sb_find_get_block(sb, sec); + if (!bh || !buffer_uptodate(bh)) + bdev_readahead(sb, sec, (u64)ra_count); + + brelse(bh); + + return 0; +} + +/* + * returns 1, if bp is flushed + * returns 0, if bp is not dirty + * returns -1, if error occurs + */ +static s32 __dcache_ent_flush(struct super_block *sb, cache_ent_t *bp, u32 sync) +{ + if (!(bp->flag & DIRTYBIT)) + return 0; +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + // Make buffer dirty (XXX: Naive impl.) + if (write_sect(sb, bp->sec, bp->bh, 0)) + return -EIO; +#endif + bp->flag &= ~(DIRTYBIT); + + if (sync) + sync_dirty_buffer(bp->bh); + + return 1; +} + +static s32 __dcache_ent_discard(struct super_block *sb, cache_ent_t *bp) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + MMSG("%s : bp[%p] (sec:%016llx flag:%08x bh:%p) list(prev:%p next:%p) " + "hash(prev:%p next:%p)\n", __func__, + bp, bp->sec, bp->flag, bp->bh, bp->prev, bp->next, + bp->hash.prev, bp->hash.next); + + __dcache_remove_hash(bp); + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + + move_to_lru(bp, &fsi->dcache.lru_list); + return 0; +} + +u8 *dcache_getblk(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + bp = __dcache_find(sb, sec); + if (bp) { + if (bdev_check_bdi_valid(sb)) { + MMSG("%s: found cache(%p, sect:%llu). But invalid BDI\n" + , __func__, bp, sec); + __dcache_ent_flush(sb, bp, 0); + __dcache_ent_discard(sb, bp); + return NULL; + } + + if (!(bp->flag & KEEPBIT)) // already in keep list + move_to_mru(bp, &fsi->dcache.lru_list); + + return bp->bh->b_data; + } + + bp = __dcache_get(sb); + + if (!__check_hash_valid(bp)) + __dcache_remove_hash(bp); + + bp->sec = sec; + bp->flag = 0; + __dcache_insert_hash(sb, bp); + + if (read_sect(sb, sec, &(bp->bh), 1)) { + __dcache_ent_discard(sb, bp); + return NULL; + } + + return bp->bh->b_data; + +} + +s32 dcache_modify(struct super_block *sb, u64 sec) +{ + s32 ret = -EIO; + cache_ent_t *bp; + + set_sb_dirty(sb); + + bp = __dcache_find(sb, sec); + if (unlikely(!bp)) { + sdfat_fs_error(sb, "Can`t find dcache (sec 0x%016llx)", sec); + return -EIO; + } +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + if (SDFAT_SB(sb)->fsi.vol_type != EXFAT) { + bp->flag |= DIRTYBIT; + return 0; + } +#endif + ret = write_sect(sb, sec, bp->bh, 0); + + if (ret) { + DMSG("%s : failed to modify buffer(err:%d, sec:%llu, bp:0x%p)\n", + __func__, ret, sec, bp); + } + + return ret; +} + +s32 dcache_lock(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + + bp = __dcache_find(sb, sec); + if (likely(bp)) { + bp->flag |= LOCKBIT; + return 0; + } + + EMSG("%s : failed to lock buffer(sec:%llu, bp:0x%p)\n", __func__, sec, bp); + return -EIO; +} + +s32 dcache_unlock(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + + bp = __dcache_find(sb, sec); + if (likely(bp)) { + bp->flag &= ~(LOCKBIT); + return 0; + } + + EMSG("%s : failed to unlock buffer (sec:%llu, bp:0x%p)\n", __func__, sec, bp); + return -EIO; +} + +s32 dcache_release(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + bp = __dcache_find(sb, sec); + if (unlikely(!bp)) + return -ENOENT; + +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + if (bp->flag & DIRTYBIT) { + if (write_sect(sb, bp->sec, bp->bh, 0)) + return -EIO; + } +#endif + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + + move_to_lru(bp, &fsi->dcache.lru_list); + return 0; +} + +s32 dcache_release_all(struct super_block *sb) +{ + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 dirtycnt = 0; + + /* Connect list elements: + * LRU list : (A - B - ... - bp_front) + (bp_first + ... + bp_last) + */ + while (fsi->dcache.keep_list.prev != &fsi->dcache.keep_list) { + cache_ent_t *bp_keep = fsi->dcache.keep_list.prev; + // bp_keep->flag &= ~(KEEPBIT); // Will be 0-ed later + move_to_mru(bp_keep, &fsi->dcache.lru_list); + } + + bp = fsi->dcache.lru_list.next; + while (bp != &fsi->dcache.lru_list) { +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + if (bp->flag & DIRTYBIT) { + dirtycnt++; + if (write_sect(sb, bp->sec, bp->bh, 0)) + ret = -EIO; + } +#endif + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + bp = bp->next; + } + + DMSG("BD:Release / dirty buf cache: %d (err:%d)", dirtycnt, ret); + return ret; +} + + +s32 dcache_flush(struct super_block *sb, u32 sync) +{ + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 dirtycnt = 0; + s32 keepcnt = 0; + + /* Connect list elements: + * LRU list : (A - B - ... - bp_front) + (bp_first + ... + bp_last) + */ + while (fsi->dcache.keep_list.prev != &fsi->dcache.keep_list) { + cache_ent_t *bp_keep = fsi->dcache.keep_list.prev; + + bp_keep->flag &= ~(KEEPBIT); // Will be 0-ed later + move_to_mru(bp_keep, &fsi->dcache.lru_list); + keepcnt++; + } + + bp = fsi->dcache.lru_list.next; + while (bp != &fsi->dcache.lru_list) { + if (bp->flag & DIRTYBIT) { +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + // Make buffer dirty (XXX: Naive impl.) + if (write_sect(sb, bp->sec, bp->bh, 0)) { + ret = -EIO; + break; + } + +#endif + bp->flag &= ~(DIRTYBIT); + dirtycnt++; + + if (sync != 0) + sync_dirty_buffer(bp->bh); + } + bp = bp->next; + } + + MMSG("BD: flush / dirty dentry cache: %d (%d from keeplist, err:%d)\n", + dirtycnt, keepcnt, ret); + return ret; +} + +static cache_ent_t *__dcache_find(struct super_block *sb, u64 sec) +{ + s32 off; + cache_ent_t *bp, *hp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + off = (sec + (sec >> fsi->sect_per_clus_bits)) & (BUF_CACHE_HASH_SIZE - 1); + + hp = &(fsi->dcache.hash_list[off]); + for (bp = hp->hash.next; bp != hp; bp = bp->hash.next) { + if (bp->sec == sec) { + touch_buffer(bp->bh); + return bp; + } + } + return NULL; +} + +static cache_ent_t *__dcache_get(struct super_block *sb) +{ + cache_ent_t *bp; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + bp = fsi->dcache.lru_list.prev; +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + while (bp->flag & (DIRTYBIT | LOCKBIT)) { + cache_ent_t *bp_prev = bp->prev; // hold prev + + if (bp->flag & DIRTYBIT) { + MMSG("BD: Buf cache => Keep list\n"); + bp->flag |= KEEPBIT; + move_to_mru(bp, &fsi->dcache.keep_list); + } + bp = bp_prev; + + /* If all dcaches are dirty */ + if (bp == &fsi->dcache.lru_list) { + DMSG("BD: buf cache flooding\n"); + dcache_flush(sb, 0); + bp = fsi->dcache.lru_list.prev; + } + } +#else + while (bp->flag & LOCKBIT) + bp = bp->prev; +#endif +// if (bp->flag & DIRTYBIT) +// sync_dirty_buffer(bp->bh); + + move_to_mru(bp, &fsi->dcache.lru_list); + return bp; +} + +static void __dcache_insert_hash(struct super_block *sb, cache_ent_t *bp) +{ + s32 off; + cache_ent_t *hp; + FS_INFO_T *fsi; + + fsi = &(SDFAT_SB(sb)->fsi); + off = (bp->sec + (bp->sec >> fsi->sect_per_clus_bits)) & (BUF_CACHE_HASH_SIZE-1); + + hp = &(fsi->dcache.hash_list[off]); + bp->hash.next = hp->hash.next; + bp->hash.prev = hp; + hp->hash.next->hash.prev = bp; + hp->hash.next = bp; +} + +static void __dcache_remove_hash(cache_ent_t *bp) +{ +#ifdef DEBUG_HASH_LIST + if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) || + (bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) { + EMSG("%s: FATAL: tried to remove already-removed-cache-entry" + "(bp:%p)\n", __func__, bp); + return; + } +#endif + WARN_ON(bp->flag & DIRTYBIT); + __remove_from_hash(bp); +} + + +/* end of cache.c */ diff --git a/fs/sdfat/config.h b/fs/sdfat/config.h new file mode 100644 index 0000000000000..6e2a4e80932c6 --- /dev/null +++ b/fs/sdfat/config.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _SDFAT_CONFIG_H +#define _SDFAT_CONFIG_H +/*======================================================================*/ +/* */ +/* FFS CONFIGURATIONS */ +/* (CHANGE THIS PART IF REQUIRED) */ +/* */ +/*======================================================================*/ + +/*----------------------------------------------------------------------*/ +/* Feature Config */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Debug/Experimental Config */ +/*----------------------------------------------------------------------*/ +//#define CONFIG_SDFAT_TRACE_IO +//#define CONFIG_SDFAT_TRACE_LOCK /* Trace elapsed time in lock_super(sb) */ + +/*----------------------------------------------------------------------*/ +/* Defragmentation Config */ +/*----------------------------------------------------------------------*/ +//#define CONFIG_SDFAT_DFR +//#define CONFIG_SDFAT_DFR_PACKING +//#define CONFIG_SDFAT_DFR_DEBUG + +/*----------------------------------------------------------------------*/ +/* Config for Kernel equal or newer than 3.7 */ +/*----------------------------------------------------------------------*/ +#ifndef CONFIG_SDFAT_WRITE_SB_INTERVAL_CSECS +#define CONFIG_SDFAT_WRITE_SB_INTERVAL_CSECS (dirty_writeback_interval) +#endif + +/*----------------------------------------------------------------------*/ +/* Default Kconfig */ +/*----------------------------------------------------------------------*/ +/* default mount options */ +#ifndef CONFIG_SDFAT_DEFAULT_CODEPAGE /* if Kconfig lacked codepage */ +#define CONFIG_SDFAT_DEFAULT_CODEPAGE 437 +#endif + +#ifndef CONFIG_SDFAT_DEFAULT_IOCHARSET /* if Kconfig lacked iocharset */ +#define CONFIG_SDFAT_DEFAULT_IOCHARSET "utf8" +#endif + +#ifndef CONFIG_SDFAT_FAT32_SHORTNAME_SEQ /* Shortname ~1, ... ~9 have higher + * priority (WIN32/VFAT-like) + */ +//#define CONFIG_SDFAT_FAT32_SHORTNAME_SEQ +#endif + +#ifndef CONFIG_SDFAT_ALIGNED_MPAGE_WRITE +//#define CONFIG_SDFAT_ALIGNED_MPAGE_WRITE +#endif + +#ifndef CONFIG_SDFAT_FAT_MIRRORING /* if Kconfig lacked fat-mirroring option */ +#define CONFIG_SDFAT_FAT_MIRRORING /* Write FAT 1, FAT 2 simultaneously */ +#endif + +#ifndef CONFIG_SDFAT_DELAYED_META_DIRTY +//#define CONFIG_SDFAT_DELAYED_META_DIRTY /* delayed DIR/FAT dirty support */ +#endif + +#ifndef CONFIG_SDFAT_SUPPORT_DIR_SYNC +//#define CONFIG_SDFAT_SUPPORT_DIR_SYNC /* support DIR_SYNC */ +#endif + +#ifndef CONFIG_SDFAT_CHECK_RO_ATTR +//#define CONFIG_SDFAT_CHECK_RO_ATTR +#endif + +#ifndef CONFIG_SDFAT_RESTRICT_EXT_ONLY_SFN +#define CONFIG_SDFAT_RESTRICT_EXT_ONLY_SFN +#endif + +#ifndef CONFIG_SDFAT_ALLOW_LOOKUP_LOSSY_SFN +//#define CONFIG_SDFAT_ALLOW_LOOKUP_LOSSY_SFN +#endif + +#ifndef CONFIG_SDFAT_DBG_SHOW_PID +//#define CONFIG_SDFAT_DBG_SHOW_PID +#endif + +#ifndef CONFIG_SDFAT_VIRTUAL_XATTR +//#define CONFIG_SDFAT_VIRTUAL_XATTR +#endif + +#ifndef CONFIG_SDFAT_SUPPORT_STLOG +//#define CONFIG_SDFAT_SUPPORT_STLOG +#endif + +#ifndef CONFIG_SDFAT_DEBUG +//{ +//#define CONFIG_SDFAT_DEBUG + +#ifndef CONFIG_SDFAT_DBG_IOCTL +//#define CONFIG_SDFAT_DBG_IOCTL +#endif + +#ifndef CONFIG_SDFAT_DBG_MSG +//#define CONFIG_SDFAT_DBG_MSG +#endif + +#ifndef CONFIG_SDFAT_DBG_CAREFUL +//#define CONFIG_SDFAT_DBG_CAREFUL +#endif + +#ifndef CONFIG_SDFAT_DBG_BUGON +//#define CONFIG_SDFAT_DBG_BUGON +#endif + +#ifndef CONFIG_SDFAT_DBG_WARNON +//#define CONFIG_SDFAT_DBG_WARNON +#endif +//} +#endif /* CONFIG_SDFAT_DEBUG */ + + +#ifndef CONFIG_SDFAT_TRACE_SB_LOCK +//#define CONFIG_SDFAT_TRACE_SB_LOCK +#endif + +#ifndef CONFIG_SDFAT_TRACE_ELAPSED_TIME +//#define CONFIG_SDFAT_TRACE_ELAPSED_TIME +#endif + +#endif /* _SDFAT_CONFIG_H */ + +/* end of config.h */ diff --git a/fs/sdfat/core.c b/fs/sdfat/core.c new file mode 100644 index 0000000000000..357eb0d54f1c9 --- /dev/null +++ b/fs/sdfat/core.c @@ -0,0 +1,3666 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : core.c */ +/* PURPOSE : FAT & exFAT common core code for sdFAT */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "sdfat.h" +#include "core.h" +#include +#include + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ +static inline void __set_sb_dirty(struct super_block *sb) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) + sb->s_dirt = 1; +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) */ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + sbi->s_dirt = 1; + /* Insert work */ + spin_lock(&sbi->work_lock); + if (!sbi->write_super_queued) { + unsigned long delay; + + delay = msecs_to_jiffies(CONFIG_SDFAT_WRITE_SB_INTERVAL_CSECS * 10); + queue_delayed_work(system_long_wq, &sbi->write_super_work, delay); + sbi->write_super_queued = 1; + } + spin_unlock(&sbi->work_lock); +#endif +} + +void set_sb_dirty(struct super_block *sb) +{ + __set_sb_dirty(sb); + // XXX: to be removed later, prints too much output + //TMSG("%s finished.\n", __func__); +} + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Local Variable Definitions */ +/*----------------------------------------------------------------------*/ + +static s8 *reserved_names[] = { + "AUX ", "CON ", "NUL ", "PRN ", + "COM1 ", "COM2 ", "COM3 ", "COM4 ", + "COM5 ", "COM6 ", "COM7 ", "COM8 ", "COM9 ", + "LPT1 ", "LPT2 ", "LPT3 ", "LPT4 ", + "LPT5 ", "LPT6 ", "LPT7 ", "LPT8 ", "LPT9 ", + NULL +}; + +/*======================================================================*/ +/* Local Function Definitions */ +/*======================================================================*/ + +/* + * File System Management Functions + */ + +static s32 check_type_size(void) +{ + /* critical check for system requirement on size of DENTRY_T structure */ + if (sizeof(DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(DOS_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(EXT_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(FILE_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(STRM_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(NAME_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(BMAP_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(CASE_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(VOLM_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + return 0; +} + +static s32 __fs_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 err; + s32 sync = 0; + + /* flags are not changed */ + if (fsi->vol_flag == new_flag) + return 0; + + fsi->vol_flag = new_flag; + + /* skip updating volume dirty flag, + * if this volume has been mounted with read-only + */ + if (sb->s_flags & MS_RDONLY) + return 0; + + if (!fsi->pbr_bh) { + err = read_sect(sb, 0, &(fsi->pbr_bh), 1); + if (err) { + EMSG("%s : failed to read boot sector\n", __func__); + return err; + } + } + + if (fsi->vol_type == EXFAT) { + pbr64_t *bpb = (pbr64_t *)fsi->pbr_bh->b_data; + bpb->bsx.vol_flags = cpu_to_le16(new_flag); + } else if (fsi->vol_type == FAT32) { + pbr32_t *bpb = (pbr32_t *)fsi->pbr_bh->b_data; + bpb->bsx.state = new_flag & VOL_DIRTY ? FAT_VOL_DIRTY : 0x00; + } else { /* FAT16/12 */ + pbr16_t *bpb = (pbr16_t *) fsi->pbr_bh->b_data; + bpb->bpb.state = new_flag & VOL_DIRTY ? FAT_VOL_DIRTY : 0x00; + } + + if (always_sync) + sync = 1; + else if ((new_flag == VOL_DIRTY) && (!buffer_dirty(fsi->pbr_bh))) + sync = 1; + else + sync = 0; + + err = write_sect(sb, 0, fsi->pbr_bh, sync); + if (err) + EMSG("%s : failed to modify volume flag\n", __func__); + + return err; +} + +static s32 fs_set_vol_flags(struct super_block *sb, u16 new_flag) +{ + return __fs_set_vol_flags(sb, new_flag, 0); +} + +s32 fscore_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) +{ + return __fs_set_vol_flags(sb, new_flag, always_sync); +} + +static inline s32 __fs_meta_sync(struct super_block *sb, s32 do_sync) +{ +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (fsi->vol_type != EXFAT) { + MMSG("meta flush in fs_sync(sync=%d)\n", do_sync); + fcache_flush(sb, 0); + dcache_flush(sb, 0); + } +#else + /* DO NOTHING */ +#endif + return 0; +} + +static s32 fs_sync(struct super_block *sb, s32 do_sync) +{ + s32 err; + + if (!do_sync) + return 0; + + err = __fs_meta_sync(sb, do_sync); + + if (!err) + err = bdev_sync_all(sb); + + if (err) + EMSG("%s : failed to sync. (err:%d)\n", __func__, err); + + return err; +} + +/* + * Cluster Management Functions + */ + +static s32 __clear_cluster(struct inode *inode, u32 clu) +{ + u64 s, n; + struct super_block *sb = inode->i_sb; + u32 sect_size = (u32)sb->s_blocksize; + s32 ret = 0; + struct buffer_head *tmp_bh = NULL; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(clu)) { /* FAT16 root_dir */ + s = fsi->root_start_sector; + n = fsi->data_start_sector; + } else { + s = CLUS_TO_SECT(fsi, clu); + n = s + fsi->sect_per_clus; + } + + if (IS_DIRSYNC(inode)) { + ret = write_msect_zero(sb, s, (u64)fsi->sect_per_clus); + if (ret != -EAGAIN) + return ret; + } + + /* Trying buffered zero writes + * if it doesn't have DIRSYNC or write_msect_zero() returned -EAGAIN + */ + for ( ; s < n; s++) { +#if 0 + dcache_release(sb, s); +#endif + ret = read_sect(sb, s, &tmp_bh, 0); + if (ret) + goto out; + + memset((u8 *)tmp_bh->b_data, 0x0, sect_size); + ret = write_sect(sb, s, tmp_bh, 0); + if (ret) + goto out; + } +out: + brelse(tmp_bh); + return ret; +} /* end of __clear_cluster */ + +static s32 __find_last_cluster(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_clu) +{ + u32 clu, next; + u32 count = 0; + + next = p_chain->dir; + if (p_chain->flags == 0x03) { + *ret_clu = next + p_chain->size - 1; + return 0; + } + + do { + count++; + clu = next; + if (fat_ent_get_safe(sb, clu, &next)) + return -EIO; + } while (!IS_CLUS_EOF(next)); + + if (p_chain->size != count) { + sdfat_fs_error(sb, "bogus directory size " + "(clus : ondisk(%d) != counted(%d))", + p_chain->size, count); + sdfat_debug_bug_on(1); + return -EIO; + } + + *ret_clu = clu; + return 0; +} + + +static s32 __count_num_clusters(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_count) +{ + u32 i, count; + u32 clu; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (!p_chain->dir || IS_CLUS_EOF(p_chain->dir)) { + *ret_count = 0; + return 0; + } + + if (p_chain->flags == 0x03) { + *ret_count = p_chain->size; + return 0; + } + + clu = p_chain->dir; + count = 0; + for (i = CLUS_BASE; i < fsi->num_clusters; i++) { + count++; + if (fat_ent_get_safe(sb, clu, &clu)) + return -EIO; + if (IS_CLUS_EOF(clu)) + break; + } + + *ret_count = count; + return 0; +} + +/* + * Upcase table Management Functions + */ +static void free_upcase_table(struct super_block *sb) +{ + u32 i; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u16 **upcase_table; + + upcase_table = fsi->vol_utbl; + for (i = 0 ; i < UTBL_COL_COUNT ; i++) { + /* kfree(NULL) is safe */ + kfree(upcase_table[i]); + upcase_table[i] = NULL; + } + + /* kfree(NULL) is safe */ + kfree(fsi->vol_utbl); + fsi->vol_utbl = NULL; +} + +static s32 __load_upcase_table(struct super_block *sb, u64 sector, u64 num_sectors, u32 utbl_checksum) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + struct buffer_head *tmp_bh = NULL; + u32 sect_size = (u32)sb->s_blocksize; + s32 ret = -EIO; + u32 i, j; + + u8 skip = false; + u32 index = 0; + u32 checksum = 0; + u16 **upcase_table = kzalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL); + + if (!upcase_table) + return -ENOMEM; + /* thanks for kzalloc + * memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *)); + */ + + fsi->vol_utbl = upcase_table; + num_sectors += sector; + + while (sector < num_sectors) { + ret = read_sect(sb, sector, &tmp_bh, 1); + if (ret) { + EMSG("%s: failed to read sector(0x%llx)\n", + __func__, sector); + goto error; + } + sector++; + + for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) { + /* FIXME : is __le16 ok? */ + //u16 uni = le16_to_cpu(((__le16*)(tmp_bh->b_data))[i]); + u16 uni = get_unaligned_le16((u8 *)tmp_bh->b_data+i); + + checksum = ((checksum & 1) ? 0x80000000 : 0) + + (checksum >> 1) + *(((u8 *)tmp_bh->b_data)+i); + checksum = ((checksum & 1) ? 0x80000000 : 0) + + (checksum >> 1) + *(((u8 *)tmp_bh->b_data)+(i+1)); + + if (skip) { + MMSG("skip from 0x%X to 0x%X(amount of 0x%X)\n", + index, index+uni, uni); + index += uni; + skip = false; + } else if (uni == index) { + index++; + } else if (uni == 0xFFFF) { + skip = true; + } else { /* uni != index , uni != 0xFFFF */ + u16 col_index = get_col_index(index); + + if (!upcase_table[col_index]) { + upcase_table[col_index] = + kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL); + if (!upcase_table[col_index]) { + EMSG("failed to allocate memory" + " for column 0x%X\n", + col_index); + ret = -ENOMEM; + goto error; + } + + for (j = 0; j < UTBL_ROW_COUNT; j++) + upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j; + } + + upcase_table[col_index][get_row_index(index)] = uni; + index++; + } + } + } + if (index >= 0xFFFF && utbl_checksum == checksum) { + DMSG("%s: load upcase table successfully" + "(idx:0x%08x, utbl_chksum:0x%08x)\n", + __func__, index, utbl_checksum); + if (tmp_bh) + brelse(tmp_bh); + return 0; + } + + EMSG("%s: failed to load upcase table" + "(idx:0x%08x, chksum:0x%08x, utbl_chksum:0x%08x)\n", + __func__, index, checksum, utbl_checksum); + + ret = -EINVAL; +error: + if (tmp_bh) + brelse(tmp_bh); + free_upcase_table(sb); + return ret; +} + +static s32 __load_default_upcase_table(struct super_block *sb) +{ + s32 i, ret = -EIO; + u32 j; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + u8 skip = false; + u32 index = 0; + u16 uni = 0; + u16 **upcase_table; + + upcase_table = kmalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL); + if (!upcase_table) + return -ENOMEM; + + fsi->vol_utbl = upcase_table; + memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *)); + + for (i = 0; index <= 0xFFFF && i < SDFAT_NUM_UPCASE*2; i += 2) { + /* FIXME : is __le16 ok? */ + //uni = le16_to_cpu(((__le16*)uni_def_upcase)[i>>1]); + uni = get_unaligned_le16((u8 *)uni_def_upcase+i); + if (skip) { + MMSG("skip from 0x%x ", index); + index += uni; + MMSG("to 0x%x (amount of 0x%x)\n", index, uni); + skip = false; + } else if (uni == index) { + index++; + } else if (uni == 0xFFFF) { + skip = true; + } else { /* uni != index , uni != 0xFFFF */ + u16 col_index = get_col_index(index); + + if (!upcase_table[col_index]) { + upcase_table[col_index] = kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL); + if (!upcase_table[col_index]) { + EMSG("failed to allocate memory for " + "new column 0x%x\n", col_index); + ret = -ENOMEM; + goto error; + } + + for (j = 0; j < UTBL_ROW_COUNT; j++) + upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j; + } + + upcase_table[col_index][get_row_index(index)] = uni; + index++; + } + } + + if (index >= 0xFFFF) + return 0; + +error: + /* FATAL error: default upcase table has error */ + free_upcase_table(sb); + return ret; +} + +static s32 load_upcase_table(struct super_block *sb) +{ + s32 i, ret; + u32 tbl_clu, type; + u64 sector, tbl_size, num_sectors; + u8 blksize_bits = sb->s_blocksize_bits; + CHAIN_T clu; + CASE_DENTRY_T *ep; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + clu.dir = fsi->root_dir; + clu.flags = 0x01; + + if (fsi->vol_type != EXFAT) + goto load_default; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < fsi->dentries_per_clu; i++) { + ep = (CASE_DENTRY_T *) get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type((DENTRY_T *) ep); + + if (type == TYPE_UNUSED) + break; + if (type != TYPE_UPCASE) + continue; + + tbl_clu = le32_to_cpu(ep->start_clu); + tbl_size = le64_to_cpu(ep->size); + + sector = CLUS_TO_SECT(fsi, tbl_clu); + num_sectors = ((tbl_size - 1) >> blksize_bits) + 1; + ret = __load_upcase_table(sb, sector, num_sectors, + le32_to_cpu(ep->checksum)); + + if (ret && (ret != -EIO)) + goto load_default; + + /* load successfully */ + return ret; + } + + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + +load_default: + sdfat_log_msg(sb, KERN_INFO, "trying to load default upcase table"); + /* load default upcase table */ + return __load_default_upcase_table(sb); +} /* end of load_upcase_table */ + + +/* + * Directory Entry Management Functions + */ +s32 walk_fat_chain(struct super_block *sb, CHAIN_T *p_dir, u32 byte_offset, u32 *clu) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u32 clu_offset; + u32 cur_clu; + + clu_offset = byte_offset >> fsi->cluster_size_bits; + cur_clu = p_dir->dir; + + if (p_dir->flags == 0x03) { + cur_clu += clu_offset; + } else { + while (clu_offset > 0) { + if (get_next_clus_safe(sb, &cur_clu)) + return -EIO; + if (IS_CLUS_EOF(cur_clu)) { + sdfat_fs_error(sb, "invalid dentry access " + "beyond EOF (clu : %u, eidx : %d)", + p_dir->dir, + byte_offset >> DENTRY_SIZE_BITS); + return -EIO; + } + clu_offset--; + } + } + + if (clu) + *clu = cur_clu; + return 0; +} + +static s32 find_location(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector, s32 *offset) +{ + s32 ret; + u32 off, clu = 0; + u32 blksize_mask = (u32)(sb->s_blocksize-1); + u8 blksize_bits = sb->s_blocksize_bits; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + off = entry << DENTRY_SIZE_BITS; + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) { + *offset = off & blksize_mask; + *sector = off >> blksize_bits; + *sector += fsi->root_start_sector; + return 0; + } + + ret = walk_fat_chain(sb, p_dir, off, &clu); + if (ret) + return ret; + + /* byte offset in cluster */ + off &= (fsi->cluster_size - 1); + + /* byte offset in sector */ + *offset = off & blksize_mask; + + /* sector offset in cluster */ + *sector = off >> blksize_bits; + *sector += CLUS_TO_SECT(fsi, clu); + return 0; +} /* end of find_location */ + +DENTRY_T *get_dentry_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u32 dentries_per_page = PAGE_SIZE >> DENTRY_SIZE_BITS; + s32 off; + u64 sec; + u8 *buf; + + if (p_dir->dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + BUG_ON(!fsi->prev_eio); + return NULL; + } + + if (find_location(sb, p_dir, entry, &sec, &off)) + return NULL; + + /* DIRECTORY READAHEAD : + * Try to read ahead per a page except root directory of fat12/16 + */ + if ((!IS_CLUS_FREE(p_dir->dir)) && + !(entry & (dentries_per_page - 1))) + dcache_readahead(sb, sec); + + buf = dcache_getblk(sb, sec); + if (!buf) + return NULL; + + if (sector) + *sector = sec; + return (DENTRY_T *)(buf + off); +} /* end of get_dentry_in_dir */ + +/* used only in search empty_slot() */ +#define CNT_UNUSED_NOHIT (-1) +#define CNT_UNUSED_HIT (-2) +/* search EMPTY CONTINUOUS "num_entries" entries */ +static s32 search_empty_slot(struct super_block *sb, HINT_FEMP_T *hint_femp, CHAIN_T *p_dir, s32 num_entries) +{ + s32 i, dentry, num_empty = 0; + s32 dentries_per_clu; + u32 type; + CHAIN_T clu; + DENTRY_T *ep; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + else + dentries_per_clu = fsi->dentries_per_clu; + + ASSERT(-1 <= hint_femp->eidx); + + if (hint_femp->eidx != -1) { + clu.dir = hint_femp->cur.dir; + clu.size = hint_femp->cur.size; + clu.flags = hint_femp->cur.flags; + + dentry = hint_femp->eidx; + + if (num_entries <= hint_femp->count) { + MMSG("%s: empty slot(HIT) - found " + "(clu : 0x%08x eidx : %d)\n", + __func__, hint_femp->cur.dir, hint_femp->eidx); + hint_femp->eidx = -1; + + if (fsi->vol_type == EXFAT) + return dentry; + + return dentry + (num_entries - 1); + } + MMSG("%s: empty slot(HIT) - search from " + "(clu : 0x%08x eidx : %d)\n", + __func__, hint_femp->cur.dir, hint_femp->eidx); + } else { + MMSG("%s: empty slot(MISS) - search from " + "(clu:0x%08x eidx : 0)\n", + __func__, p_dir->dir); + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + dentry = 0; + } + + while (!IS_CLUS_EOF(clu.dir)) { + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + i = dentry % dentries_per_clu; + else + i = dentry & (dentries_per_clu-1); + + for ( ; i < dentries_per_clu; i++, dentry++) { + ep = get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type(ep); + + if ((type == TYPE_UNUSED) || (type == TYPE_DELETED)) { + num_empty++; + if (hint_femp->eidx == -1) { + hint_femp->eidx = dentry; + hint_femp->count = CNT_UNUSED_NOHIT; + + hint_femp->cur.dir = clu.dir; + hint_femp->cur.size = clu.size; + hint_femp->cur.flags = clu.flags; + } + + if ((type == TYPE_UNUSED) && + (hint_femp->count != CNT_UNUSED_HIT)) { + hint_femp->count = CNT_UNUSED_HIT; + } + } else { + if ((hint_femp->eidx != -1) && + (hint_femp->count == CNT_UNUSED_HIT)) { + /* unused empty group means + * an empty group which includes + * unused dentry + */ + sdfat_fs_error(sb, + "found bogus dentry(%d) " + "beyond unused empty group(%d) " + "(start_clu : %u, cur_clu : %u)", + dentry, hint_femp->eidx, p_dir->dir, + clu.dir); + return -EIO; + } + + num_empty = 0; + hint_femp->eidx = -1; + } + + if (num_empty >= num_entries) { + /* found and invalidate hint_femp */ + hint_femp->eidx = -1; + + if (fsi->vol_type == EXFAT) + return (dentry - (num_entries-1)); + + return dentry; + } + } + + if (IS_CLUS_FREE(p_dir->dir)) + break; /* FAT16 root_dir */ + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + } + + return -ENOSPC; +} /* end of search_empty_slot */ + +/* find empty directory entry. + * if there isn't any empty slot, expand cluster chain. + */ +static s32 find_empty_entry(struct inode *inode, CHAIN_T *p_dir, s32 num_entries) +{ + s32 dentry; + u32 ret, last_clu; + u64 sector; + u64 size = 0; + CHAIN_T clu; + DENTRY_T *ep = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + HINT_FEMP_T hint_femp; + + hint_femp.eidx = -1; + + ASSERT(-1 <= fid->hint_femp.eidx); + + if (fid->hint_femp.eidx != -1) { + memcpy(&hint_femp, &fid->hint_femp, sizeof(HINT_FEMP_T)); + fid->hint_femp.eidx = -1; + } + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + return search_empty_slot(sb, &hint_femp, p_dir, num_entries); + + while ((dentry = search_empty_slot(sb, &hint_femp, p_dir, num_entries)) < 0) { + if (dentry == -EIO) + break; + + if (fsi->fs_func->check_max_dentries(fid)) + return -ENOSPC; + + /* we trust p_dir->size regardless of FAT type */ + if (__find_last_cluster(sb, p_dir, &last_clu)) + return -EIO; + + /* + * Allocate new cluster to this directory + */ + clu.dir = last_clu + 1; + clu.size = 0; /* UNUSED */ + clu.flags = p_dir->flags; + + /* (0) check if there are reserved clusters + * (create_dir 의 주석 참고) + */ + if (!IS_CLUS_EOF(fsi->used_clusters) && + ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - 2))) + return -ENOSPC; + + /* (1) allocate a cluster */ + ret = fsi->fs_func->alloc_cluster(sb, 1, &clu, ALLOC_HOT); + if (ret) + return ret; + + if (__clear_cluster(inode, clu.dir)) + return -EIO; + + /* (2) append to the FAT chain */ + if (clu.flags != p_dir->flags) { + /* no-fat-chain bit is disabled, + * so fat-chain should be synced with alloc-bmp + */ + chain_cont_cluster(sb, p_dir->dir, p_dir->size); + p_dir->flags = 0x01; + hint_femp.cur.flags = 0x01; + } + + if (clu.flags == 0x01) + if (fat_ent_set(sb, last_clu, clu.dir)) + return -EIO; + + if (hint_femp.eidx == -1) { + /* the special case that new dentry + * should be allocated from the start of new cluster + */ + hint_femp.eidx = (s32)(p_dir->size << + (fsi->cluster_size_bits - DENTRY_SIZE_BITS)); + hint_femp.count = fsi->dentries_per_clu; + + hint_femp.cur.dir = clu.dir; + hint_femp.cur.size = 0; + hint_femp.cur.flags = clu.flags; + } + hint_femp.cur.size++; + p_dir->size++; + size = (p_dir->size << fsi->cluster_size_bits); + + /* (3) update the directory entry */ + if ((fsi->vol_type == EXFAT) && (p_dir->dir != fsi->root_dir)) { + ep = get_dentry_in_dir(sb, + &(fid->dir), fid->entry+1, §or); + if (!ep) + return -EIO; + fsi->fs_func->set_entry_size(ep, size); + fsi->fs_func->set_entry_flag(ep, p_dir->flags); + if (dcache_modify(sb, sector)) + return -EIO; + + if (update_dir_chksum(sb, &(fid->dir), fid->entry)) + return -EIO; + } + + /* directory inode should be updated in here */ + i_size_write(inode, (loff_t)size); + SDFAT_I(inode)->i_size_ondisk += fsi->cluster_size; + SDFAT_I(inode)->i_size_aligned += fsi->cluster_size; + SDFAT_I(inode)->fid.size = size; + SDFAT_I(inode)->fid.flags = p_dir->flags; + inode->i_blocks += 1 << (fsi->cluster_size_bits - sb->s_blocksize_bits); + } + + return dentry; +} /* end of find_empty_entry */ + +#define SDFAT_MIN_SUBDIR (2) +static const char *dot_name[SDFAT_MIN_SUBDIR] = { DOS_CUR_DIR_NAME, DOS_PAR_DIR_NAME }; + +static s32 __count_dos_name_entries(struct super_block *sb, CHAIN_T *p_dir, u32 type, u32 *dotcnt) +{ + s32 i, count = 0, check_dot = 0; + s32 dentries_per_clu; + u32 entry_type; + CHAIN_T clu; + DENTRY_T *ep; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + else + dentries_per_clu = fsi->dentries_per_clu; + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + if (dotcnt) { + *dotcnt = 0; + if (fsi->vol_type != EXFAT) + check_dot = 1; + } + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < dentries_per_clu; i++) { + ep = get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + entry_type = fsi->fs_func->get_entry_type(ep); + + if (entry_type == TYPE_UNUSED) + return count; + if (!(type & TYPE_CRITICAL_PRI) && !(type & TYPE_BENIGN_PRI)) + continue; + + if ((type != TYPE_ALL) && (type != entry_type)) + continue; + + count++; + if (check_dot && (i < SDFAT_MIN_SUBDIR)) { + BUG_ON(fsi->vol_type == EXFAT); + /* 11 is DOS_NAME_LENGTH */ + if (!strncmp(ep->dummy, dot_name[i], 11)) + (*dotcnt)++; + } + } + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + break; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + + check_dot = 0; + } + + return count; +} + +s32 check_dir_empty(struct super_block *sb, CHAIN_T *p_dir) +{ + s32 i, count = 0; + s32 dentries_per_clu; + u32 type; + CHAIN_T clu; + DENTRY_T *ep; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + else + dentries_per_clu = fsi->dentries_per_clu; + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < dentries_per_clu; i++) { + ep = get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type(ep); + + if (type == TYPE_UNUSED) + return 0; + + if ((type != TYPE_FILE) && (type != TYPE_DIR)) + continue; + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + return -ENOTEMPTY; + + if (fsi->vol_type == EXFAT) + return -ENOTEMPTY; + + if ((p_dir->dir == fsi->root_dir) || (++count > 2)) + return -ENOTEMPTY; + } + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + return -ENOTEMPTY; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + } + + return 0; +} + +/* + * Name Conversion Functions + */ +#ifdef CONFIG_SDFAT_ALLOW_LOOKUP_LOSSY_SFN + /* over name length only */ +#define NEED_INVALIDATE_SFN(x) ((x) & NLS_NAME_OVERLEN) +#else + /* all lossy case */ +#define NEED_INVALIDATE_SFN(x) (x) +#endif + +/* NOTE : + * We should keep shortname code compatible with v1.0.15 or lower + * So, we try to check ext-only-name at create-mode only. + * + * i.e. '.mtp' -> + * v1.0.15 : ' MTP' with name_case, 0x10 + * v1.1.0 : 'MT????~?' with name_case, 0x00 and longname. + */ +static inline void preprocess_ext_only_sfn(s32 lookup, u16 first_char, DOS_NAME_T *p_dosname, s32 *lossy) +{ +#ifdef CONFIG_SDFAT_RESTRICT_EXT_ONLY_SFN + int i; + /* check ext-only-name at create-mode */ + if (*lossy || lookup || (first_char != (u16)'.')) + return; + + p_dosname->name_case = 0xFF; + + /* move ext-name to base-name */ + for (i = 0; i < 3; i++) { + p_dosname->name[i] = p_dosname->name[8+i]; + if (p_dosname->name[i] == ' ') + p_dosname->name[i] = '_'; + } + + /* fill remained space with '_' */ + for (i = 3; i < 8; i++) + p_dosname->name[i] = '_'; + + /* eliminate ext-name */ + for (i = 8; i < 11; i++) + p_dosname->name[i] = ' '; + + *lossy = NLS_NAME_LOSSY; +#endif /* CONFIG_SDFAT_CAN_CREATE_EXT_ONLY_SFN */ +} + +/* input : dir, uni_name + * output : num_of_entry, dos_name(format : aaaaaa~1.bbb) + */ +static s32 get_num_entries_and_dos_name(struct super_block *sb, CHAIN_T *p_dir, + UNI_NAME_T *p_uniname, s32 *entries, + DOS_NAME_T *p_dosname, s32 lookup) +{ + s32 ret, num_entries, lossy = NLS_NAME_NO_LOSSY; + s8 **r; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* Init null char. */ + p_dosname->name[0] = '\0'; + + num_entries = fsi->fs_func->calc_num_entries(p_uniname); + if (num_entries == 0) + return -EINVAL; + + if (fsi->vol_type == EXFAT) + goto out; + + nls_uni16s_to_sfn(sb, p_uniname, p_dosname, &lossy); + + preprocess_ext_only_sfn(lookup, p_uniname->name[0], p_dosname, &lossy); + + if (!lossy) { + for (r = reserved_names; *r; r++) { + if (!strncmp((void *) p_dosname->name, *r, 8)) + return -EINVAL; + } + + if (p_dosname->name_case != 0xFF) + num_entries = 1; + } else if (!lookup) { + /* create new dos name */ + ret = fat_generate_dos_name_new(sb, p_dir, p_dosname, + num_entries); + if (ret) + return ret; + + } else if (NEED_INVALIDATE_SFN(lossy)) { + /* FIXME : We should check num_entries */ + p_dosname->name[0] = '\0'; + } + + if (num_entries > 1) + p_dosname->name_case = 0x0; +out: + *entries = num_entries; + return 0; +} /* end of get_num_entries_and_dos_name */ + +void get_uniname_from_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, UNI_NAME_T *p_uniname, u8 mode) +{ + DOS_NAME_T dos_name; + + if (mode == 0x0) + dos_name.name_case = 0x0; + else + dos_name.name_case = ep->lcase; + + memcpy(dos_name.name, ep->name, DOS_NAME_LENGTH); + nls_sfn_to_uni16s(sb, &dos_name, p_uniname); +} /* end of get_uniname_from_dos_entry */ + +/* returns the length of a struct qstr, ignoring trailing dots */ +static inline unsigned int __striptail_len(unsigned int len, const char *name) +{ + while (len && name[len - 1] == '.') + len--; + return len; +} + +/* + * Name Resolution Functions : + * Zero if it was successful; otherwise nonzero. + */ +static s32 __resolve_path(struct inode *inode, const u8 *path, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, int lookup) +{ + s32 namelen; + s32 lossy = NLS_NAME_NO_LOSSY; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + + /* DOT and DOTDOT are handled by VFS layer */ + + /* strip all trailing spaces */ + /* DO NOTHING : Is needed? */ + + /* strip all trailing periods */ + namelen = __striptail_len(strlen(path), path); + if (!namelen) + return -ENOENT; + + /* the limitation of linux? */ + if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE)) + return -ENAMETOOLONG; + + /* + * strip all leading spaces : + * "MS windows 7" supports leading spaces. + * So we should skip this preprocessing for compatibility. + */ + + /* file name conversion : + * If lookup case, we allow bad-name for compatibility. + */ + namelen = nls_vfsname_to_uni16s(sb, path, namelen, p_uniname, &lossy); + if (namelen < 0) + return namelen; /* return error value */ + + if ((lossy && !lookup) || !namelen) + return -EINVAL; + + sdfat_debug_bug_on(fid->size != i_size_read(inode)); +// fid->size = i_size_read(inode); + + p_dir->dir = fid->start_clu; + p_dir->size = (u32)(fid->size >> fsi->cluster_size_bits); + p_dir->flags = fid->flags; + + return 0; +} + +static inline s32 resolve_path(struct inode *inode, const u8 *path, CHAIN_T *dir, UNI_NAME_T *uni) +{ + return __resolve_path(inode, path, dir, uni, 0); +} + +static inline s32 resolve_path_for_lookup(struct inode *inode, const u8 *path, CHAIN_T *dir, UNI_NAME_T *uni) +{ + return __resolve_path(inode, path, dir, uni, 1); +} + +static s32 create_dir(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, FILE_ID_T *fid) +{ + s32 dentry, num_entries; + u64 ret; + u64 size; + CHAIN_T clu; + DOS_NAME_T dos_name, dot_name; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name, 0); + if (ret) + return ret; + + /* find_empty_entry must be called before alloc_cluster */ + dentry = find_empty_entry(inode, p_dir, num_entries); + if (dentry < 0) + return dentry; /* -EIO or -ENOSPC */ + + clu.dir = CLUS_EOF; + clu.size = 0; + clu.flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; + + /* (0) Check if there are reserved clusters up to max. */ + if ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - CLUS_BASE)) + return -ENOSPC; + + /* (1) allocate a cluster */ + ret = fsi->fs_func->alloc_cluster(sb, 1, &clu, ALLOC_HOT); + if (ret) + return ret; + + ret = __clear_cluster(inode, clu.dir); + if (ret) + return ret; + + size = fsi->cluster_size; + if (fsi->vol_type != EXFAT) { + /* initialize the . and .. entry + * Information for . points to itself + * Information for .. points to parent dir + */ + + dot_name.name_case = 0x0; + memcpy(dot_name.name, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH); + + ret = fsi->fs_func->init_dir_entry(sb, &clu, 0, TYPE_DIR, clu.dir, 0); + if (ret) + return ret; + + ret = fsi->fs_func->init_ext_entry(sb, &clu, 0, 1, NULL, &dot_name); + if (ret) + return ret; + + memcpy(dot_name.name, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH); + + if (p_dir->dir == fsi->root_dir) + ret = fsi->fs_func->init_dir_entry(sb, &clu, 1, TYPE_DIR, CLUS_FREE, 0); + else + ret = fsi->fs_func->init_dir_entry(sb, &clu, 1, TYPE_DIR, p_dir->dir, 0); + + if (ret) + return ret; + + ret = fsi->fs_func->init_ext_entry(sb, &clu, 1, 1, NULL, &dot_name); + if (ret) + return ret; + } + + /* (2) update the directory entry */ + /* make sub-dir entry in parent directory */ + ret = fsi->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_DIR, clu.dir, size); + if (ret) + return ret; + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fid->dir.dir = p_dir->dir; + fid->dir.size = p_dir->size; + fid->dir.flags = p_dir->flags; + fid->entry = dentry; + + fid->attr = ATTR_SUBDIR; + fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; + fid->size = size; + fid->start_clu = clu.dir; + + fid->type = TYPE_DIR; + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + + /* hint_stat will be used if this is directory. */ + fid->version = 0; + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + return 0; +} /* end of create_dir */ + +static s32 create_file(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, u8 mode, FILE_ID_T *fid) +{ + s32 ret, dentry, num_entries; + DOS_NAME_T dos_name; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name, 0); + if (ret) + return ret; + + /* find_empty_entry must be called before alloc_cluster() */ + dentry = find_empty_entry(inode, p_dir, num_entries); + if (dentry < 0) + return dentry; /* -EIO or -ENOSPC */ + + /* (1) update the directory entry */ + /* fill the dos name directory entry information of the created file. + * the first cluster is not determined yet. (0) + */ + ret = fsi->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_FILE | mode, CLUS_FREE, 0); + if (ret) + return ret; + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fid->dir.dir = p_dir->dir; + fid->dir.size = p_dir->size; + fid->dir.flags = p_dir->flags; + fid->entry = dentry; + + fid->attr = ATTR_ARCHIVE | mode; + fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; + fid->size = 0; + fid->start_clu = CLUS_EOF; + + fid->type = TYPE_FILE; + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + + /* hint_stat will be used if this is directory. */ + fid->version = 0; + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + return 0; +} /* end of create_file */ + +static s32 remove_file(struct inode *inode, CHAIN_T *p_dir, s32 entry) +{ + s32 num_entries; + u64 sector; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + ep = get_dentry_in_dir(sb, p_dir, entry, §or); + if (!ep) + return -EIO; + + dcache_lock(sb, sector); + + /* dcache_lock() before call count_ext_entries() */ + num_entries = fsi->fs_func->count_ext_entries(sb, p_dir, entry, ep); + if (num_entries < 0) { + dcache_unlock(sb, sector); + return -EIO; + } + num_entries++; + + dcache_unlock(sb, sector); + + /* (1) update the directory entry */ + return fsi->fs_func->delete_dir_entry(sb, p_dir, entry, 0, num_entries); +} /* end of remove_file */ + +static s32 rename_file(struct inode *inode, CHAIN_T *p_dir, s32 oldentry, UNI_NAME_T *p_uniname, FILE_ID_T *fid) +{ + s32 ret, newentry = -1, num_old_entries, num_new_entries; + u64 sector_old, sector_new; + DOS_NAME_T dos_name; + DENTRY_T *epold, *epnew; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + epold = get_dentry_in_dir(sb, p_dir, oldentry, §or_old); + if (!epold) + return -EIO; + + dcache_lock(sb, sector_old); + + /* dcache_lock() before call count_ext_entries() */ + num_old_entries = fsi->fs_func->count_ext_entries(sb, p_dir, oldentry, epold); + if (num_old_entries < 0) { + dcache_unlock(sb, sector_old); + return -EIO; + } + num_old_entries++; + + ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_new_entries, &dos_name, 0); + if (ret) { + dcache_unlock(sb, sector_old); + return ret; + } + + if (num_old_entries < num_new_entries) { + newentry = find_empty_entry(inode, p_dir, num_new_entries); + if (newentry < 0) { + dcache_unlock(sb, sector_old); + return newentry; /* -EIO or -ENOSPC */ + } + + epnew = get_dentry_in_dir(sb, p_dir, newentry, §or_new); + if (!epnew) { + dcache_unlock(sb, sector_old); + return -EIO; + } + + memcpy((void *) epnew, (void *) epold, DENTRY_SIZE); + if (fsi->fs_func->get_entry_type(epnew) == TYPE_FILE) { + fsi->fs_func->set_entry_attr(epnew, fsi->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE); + fid->attr |= ATTR_ARCHIVE; + } + dcache_modify(sb, sector_new); + dcache_unlock(sb, sector_old); + + if (fsi->vol_type == EXFAT) { + epold = get_dentry_in_dir(sb, p_dir, oldentry+1, §or_old); + dcache_lock(sb, sector_old); + epnew = get_dentry_in_dir(sb, p_dir, newentry+1, §or_new); + + if (!epold || !epnew) { + dcache_unlock(sb, sector_old); + return -EIO; + } + + memcpy((void *) epnew, (void *) epold, DENTRY_SIZE); + dcache_modify(sb, sector_new); + dcache_unlock(sb, sector_old); + } + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, newentry, num_new_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fsi->fs_func->delete_dir_entry(sb, p_dir, oldentry, 0, num_old_entries); + fid->entry = newentry; + } else { + if (fsi->fs_func->get_entry_type(epold) == TYPE_FILE) { + fsi->fs_func->set_entry_attr(epold, fsi->fs_func->get_entry_attr(epold) | ATTR_ARCHIVE); + fid->attr |= ATTR_ARCHIVE; + } + dcache_modify(sb, sector_old); + dcache_unlock(sb, sector_old); + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, oldentry, num_new_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fsi->fs_func->delete_dir_entry(sb, p_dir, oldentry, num_new_entries, num_old_entries); + } + + return 0; +} /* end of rename_file */ + +static s32 move_file(struct inode *inode, CHAIN_T *p_olddir, s32 oldentry, + CHAIN_T *p_newdir, UNI_NAME_T *p_uniname, FILE_ID_T *fid) +{ + s32 ret, newentry, num_new_entries, num_old_entries; + u64 sector_mov, sector_new; + CHAIN_T clu; + DOS_NAME_T dos_name; + DENTRY_T *epmov, *epnew; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + epmov = get_dentry_in_dir(sb, p_olddir, oldentry, §or_mov); + if (!epmov) + return -EIO; + + /* check if the source and target directory is the same */ + if (fsi->fs_func->get_entry_type(epmov) == TYPE_DIR && + fsi->fs_func->get_entry_clu0(epmov) == p_newdir->dir) + return -EINVAL; + + dcache_lock(sb, sector_mov); + + /* dcache_lock() before call count_ext_entries() */ + num_old_entries = fsi->fs_func->count_ext_entries(sb, p_olddir, oldentry, epmov); + if (num_old_entries < 0) { + dcache_unlock(sb, sector_mov); + return -EIO; + } + num_old_entries++; + + ret = get_num_entries_and_dos_name(sb, p_newdir, p_uniname, &num_new_entries, &dos_name, 0); + if (ret) { + dcache_unlock(sb, sector_mov); + return ret; + } + + newentry = find_empty_entry(inode, p_newdir, num_new_entries); + if (newentry < 0) { + dcache_unlock(sb, sector_mov); + return newentry; /* -EIO or -ENOSPC */ + } + + epnew = get_dentry_in_dir(sb, p_newdir, newentry, §or_new); + if (!epnew) { + dcache_unlock(sb, sector_mov); + return -EIO; + } + + memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE); + if (fsi->fs_func->get_entry_type(epnew) == TYPE_FILE) { + fsi->fs_func->set_entry_attr(epnew, fsi->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE); + fid->attr |= ATTR_ARCHIVE; + } + dcache_modify(sb, sector_new); + dcache_unlock(sb, sector_mov); + + if (fsi->vol_type == EXFAT) { + epmov = get_dentry_in_dir(sb, p_olddir, oldentry+1, §or_mov); + dcache_lock(sb, sector_mov); + epnew = get_dentry_in_dir(sb, p_newdir, newentry+1, §or_new); + if (!epmov || !epnew) { + dcache_unlock(sb, sector_mov); + return -EIO; + } + + memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE); + dcache_modify(sb, sector_new); + dcache_unlock(sb, sector_mov); + } else if (fsi->fs_func->get_entry_type(epnew) == TYPE_DIR) { + /* change ".." pointer to new parent dir */ + clu.dir = fsi->fs_func->get_entry_clu0(epnew); + clu.flags = 0x01; + + epnew = get_dentry_in_dir(sb, &clu, 1, §or_new); + if (!epnew) + return -EIO; + + if (p_newdir->dir == fsi->root_dir) + fsi->fs_func->set_entry_clu0(epnew, CLUS_FREE); + else + fsi->fs_func->set_entry_clu0(epnew, p_newdir->dir); + dcache_modify(sb, sector_new); + } + + ret = fsi->fs_func->init_ext_entry(sb, p_newdir, newentry, num_new_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fsi->fs_func->delete_dir_entry(sb, p_olddir, oldentry, 0, num_old_entries); + + fid->dir.dir = p_newdir->dir; + fid->dir.size = p_newdir->size; + fid->dir.flags = p_newdir->flags; + + fid->entry = newentry; + + return 0; +} /* end of move_file */ + + +/*======================================================================*/ +/* Global Function Definitions */ +/*======================================================================*/ +/* roll back to the initial state of the file system */ +s32 fscore_init(void) +{ + s32 ret; + + ret = check_type_size(); + if (ret) + return ret; + + return extent_cache_init(); +} + +/* make free all memory-alloced global buffers */ +s32 fscore_shutdown(void) +{ + extent_cache_shutdown(); + return 0; +} + +static bool is_exfat(pbr_t *pbr) +{ + int i = 53; + + do { + if (pbr->bpb.f64.res_zero[i-1]) + break; + } while (--i); + return i ? false : true; +} + +static bool is_fat32(pbr_t *pbr) +{ + if (le16_to_cpu(pbr->bpb.f16.num_fat_sectors)) + return false; + return true; +} + +inline pbr_t *read_pbr_with_logical_sector(struct super_block *sb, struct buffer_head **prev_bh) +{ + pbr_t *p_pbr = (pbr_t *) (*prev_bh)->b_data; + u16 logical_sect = 0; + + if (is_exfat(p_pbr)) + logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; + else + logical_sect = get_unaligned_le16(&p_pbr->bpb.f16.sect_size); + + /* is x a power of 2? + * (x) != 0 && (((x) & ((x) - 1)) == 0) + */ + if (!is_power_of_2(logical_sect) + || (logical_sect < 512) + || (logical_sect > 4096)) { + sdfat_log_msg(sb, KERN_ERR, "bogus logical sector size %u", + logical_sect); + return NULL; + } + + if (logical_sect < sb->s_blocksize) { + sdfat_log_msg(sb, KERN_ERR, + "logical sector size too small for device" + " (logical sector size = %u)", logical_sect); + return NULL; + } + + if (logical_sect > sb->s_blocksize) { + struct buffer_head *bh = NULL; + + __brelse(*prev_bh); + *prev_bh = NULL; + + if (!sb_set_blocksize(sb, logical_sect)) { + sdfat_log_msg(sb, KERN_ERR, + "unable to set blocksize %u", logical_sect); + return NULL; + } + bh = sb_bread(sb, 0); + if (!bh) { + sdfat_log_msg(sb, KERN_ERR, + "unable to read boot sector " + "(logical sector size = %lu)", sb->s_blocksize); + return NULL; + } + + *prev_bh = bh; + p_pbr = (pbr_t *) bh->b_data; + } + + sdfat_log_msg(sb, KERN_INFO, + "set logical sector size : %lu", sb->s_blocksize); + + return p_pbr; +} + +/* mount the file system volume */ +s32 fscore_mount(struct super_block *sb) +{ + s32 ret; + pbr_t *p_pbr; + struct buffer_head *tmp_bh = NULL; + struct gendisk *disk = sb->s_bdev->bd_disk; + struct sdfat_mount_options *opts = &(SDFAT_SB(sb)->options); + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* initialize previous I/O error */ + fsi->prev_eio = 0; + + /* open the block device */ + if (bdev_open_dev(sb)) + return -EIO; + + /* set block size to read super block */ + sb_min_blocksize(sb, 512); + + /* read boot sector */ + ret = read_sect(sb, 0, &tmp_bh, 1); + if (ret) { + sdfat_log_msg(sb, KERN_ERR, "unable to read boot sector"); + ret = -EIO; + goto bd_close; + } + + /* PRB is read */ + p_pbr = (pbr_t *) tmp_bh->b_data; + + /* check the validity of PBR */ + if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { + sdfat_log_msg(sb, KERN_ERR, "invalid boot record signature"); + brelse(tmp_bh); + ret = -EINVAL; + goto bd_close; + } + + /* check logical sector size */ + p_pbr = read_pbr_with_logical_sector(sb, &tmp_bh); + if (!p_pbr) { + brelse(tmp_bh); + ret = -EIO; + goto bd_close; + } + + /* fill fs_struct */ + if (is_exfat(p_pbr)) { + if (opts->fs_type && opts->fs_type != FS_TYPE_EXFAT) { + sdfat_log_msg(sb, KERN_ERR, + "not specified filesystem type " + "(media:exfat, opts:%s)", + FS_TYPE_STR[opts->fs_type]); + ret = -EINVAL; + goto free_bh; + } + /* set maximum file size for exFAT */ + sb->s_maxbytes = 0x7fffffffffffffffLL; + opts->improved_allocation = 0; + opts->defrag = 0; + ret = mount_exfat(sb, p_pbr); + } else if (is_fat32(p_pbr)) { + if (opts->fs_type && opts->fs_type != FS_TYPE_VFAT) { + sdfat_log_msg(sb, KERN_ERR, + "not specified filesystem type " + "(media:vfat, opts:%s)", + FS_TYPE_STR[opts->fs_type]); + ret = -EINVAL; + goto free_bh; + } + /* set maximum file size for FAT */ + sb->s_maxbytes = 0xffffffff; + ret = mount_fat32(sb, p_pbr); + } else { + if (opts->fs_type && opts->fs_type != FS_TYPE_VFAT) { + sdfat_log_msg(sb, KERN_ERR, + "not specified filesystem type " + "(media:vfat, opts:%s)", + FS_TYPE_STR[opts->fs_type]); + ret = -EINVAL; + goto free_bh; + } + /* set maximum file size for FAT */ + sb->s_maxbytes = 0xffffffff; + opts->improved_allocation = 0; + opts->defrag = 0; + ret = mount_fat16(sb, p_pbr); + } +free_bh: + brelse(tmp_bh); + if (ret) { + sdfat_log_msg(sb, KERN_ERR, "failed to mount fs-core"); + goto bd_close; + } + + /* warn misaligned data data start sector must be a multiple of clu_size */ + sdfat_log_msg(sb, KERN_INFO, + "detected volume info : %s " + "(bps : %lu, spc : %u, data start : %llu, %s)", + sdfat_get_vol_type_str(fsi->vol_type), + sb->s_blocksize, fsi->sect_per_clus, fsi->data_start_sector, + (fsi->data_start_sector & (fsi->sect_per_clus - 1)) ? + "misaligned" : "aligned"); + + sdfat_log_msg(sb, KERN_INFO, + "detected volume size : %llu MB (disk_size : %llu MB)", + fsi->num_sectors >> 11, + disk ? (u64)((disk->part0.nr_sects) >> 11) : 0); + + ret = load_upcase_table(sb); + if (ret) { + sdfat_log_msg(sb, KERN_ERR, "failed to load upcase table"); + goto bd_close; + } + + if (fsi->vol_type != EXFAT) + goto update_used_clus; + + /* allocate-bitmap is only for exFAT */ + ret = load_alloc_bmp(sb); + if (ret) { + sdfat_log_msg(sb, KERN_ERR, "failed to load alloc-bitmap"); + goto free_upcase; + } + +update_used_clus: + if (fsi->used_clusters == (u32) ~0) { + ret = fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters); + if (ret) { + sdfat_log_msg(sb, KERN_ERR, "failed to scan clusters"); + goto free_alloc_bmp; + } + } + + return 0; +free_alloc_bmp: + if (fsi->vol_type == EXFAT) + free_alloc_bmp(sb); +free_upcase: + free_upcase_table(sb); +bd_close: + bdev_close_dev(sb); + return ret; +} /* end of fscore_mount */ + +/* umount the file system volume */ +s32 fscore_umount(struct super_block *sb) +{ + s32 ret = 0; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (fs_sync(sb, 0)) + ret = -EIO; + + if (fs_set_vol_flags(sb, VOL_CLEAN)) + ret = -EIO; + + free_upcase_table(sb); + + if (fsi->vol_type == EXFAT) + free_alloc_bmp(sb); + + if (fcache_release_all(sb)) + ret = -EIO; + + if (dcache_release_all(sb)) + ret = -EIO; + + amap_destroy(sb); + + if (fsi->prev_eio) + ret = -EIO; + /* close the block device */ + bdev_close_dev(sb); + return ret; +} + +/* get the information of a file system volume */ +s32 fscore_statfs(struct super_block *sb, VOL_INFO_T *info) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (fsi->used_clusters == (u32) ~0) { + if (fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters)) + return -EIO; + } + + info->FatType = fsi->vol_type; + info->ClusterSize = fsi->cluster_size; + info->NumClusters = fsi->num_clusters - 2; /* clu 0 & 1 */ + info->UsedClusters = fsi->used_clusters + fsi->reserved_clusters; + info->FreeClusters = info->NumClusters - info->UsedClusters; + + return 0; +} + +/* synchronize all file system volumes */ +s32 fscore_sync_fs(struct super_block *sb, s32 do_sync) +{ + /* synchronize the file system */ + if (fs_sync(sb, do_sync)) + return -EIO; + + if (fs_set_vol_flags(sb, VOL_CLEAN)) + return -EIO; + + return 0; +} + +/* stat allocation unit of a file system volume */ +u32 fscore_get_au_stat(struct super_block *sb, s32 mode) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (fsi->fs_func->get_au_stat) + return fsi->fs_func->get_au_stat(sb, mode); + + /* No error, just returns 0 */ + return 0; +} + + +/*----------------------------------------------------------------------*/ +/* File Operation Functions */ +/*----------------------------------------------------------------------*/ +/* lookup a file */ +s32 fscore_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid) +{ + s32 ret, dentry, num_entries; + CHAIN_T dir; + UNI_NAME_T uni_name; + DOS_NAME_T dos_name; + DENTRY_T *ep, *ep2; + ENTRY_SET_CACHE_T *es = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *dir_fid = &(SDFAT_I(inode)->fid); + + TMSG("%s entered\n", __func__); + + /* check the validity of directory name in the given pathname */ + ret = resolve_path_for_lookup(inode, path, &dir, &uni_name); + if (ret) + return ret; + + ret = get_num_entries_and_dos_name(sb, &dir, &uni_name, &num_entries, &dos_name, 1); + if (ret) + return ret; + + /* check the validation of hint_stat and initialize it if required */ + if (dir_fid->version != (u32)(inode->i_version & 0xffffffff)) { + dir_fid->hint_stat.clu = dir.dir; + dir_fid->hint_stat.eidx = 0; + dir_fid->version = (u32)(inode->i_version & 0xffffffff); + dir_fid->hint_femp.eidx = -1; + } + + /* search the file name for directories */ + dentry = fsi->fs_func->find_dir_entry(sb, dir_fid, &dir, &uni_name, + num_entries, &dos_name, TYPE_ALL); + + if ((dentry < 0) && (dentry != -EEXIST)) + return dentry; /* -error value */ + + fid->dir.dir = dir.dir; + fid->dir.size = dir.size; + fid->dir.flags = dir.flags; + fid->entry = dentry; + + /* root directory itself */ + if (unlikely(dentry == -EEXIST)) { + fid->type = TYPE_DIR; + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + + fid->attr = ATTR_SUBDIR; + fid->flags = 0x01; + fid->size = 0; + fid->start_clu = fsi->root_dir; + } else { + if (fsi->vol_type == EXFAT) { + es = get_dentry_set_in_dir(sb, &dir, dentry, ES_2_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + } else { + ep = get_dentry_in_dir(sb, &dir, dentry, NULL); + if (!ep) + return -EIO; + ep2 = ep; + } + + fid->type = fsi->fs_func->get_entry_type(ep); + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + fid->attr = fsi->fs_func->get_entry_attr(ep); + + fid->size = fsi->fs_func->get_entry_size(ep2); + if ((fid->type == TYPE_FILE) && (fid->size == 0)) { + fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; + fid->start_clu = CLUS_EOF; + } else { + fid->flags = fsi->fs_func->get_entry_flag(ep2); + fid->start_clu = fsi->fs_func->get_entry_clu0(ep2); + } + + if ((fid->type == TYPE_DIR) && (fsi->vol_type != EXFAT)) { + u32 num_clu = 0; + CHAIN_T tmp_dir; + + tmp_dir.dir = fid->start_clu; + tmp_dir.flags = fid->flags; + tmp_dir.size = 0; /* UNUSED */ + + if (__count_num_clusters(sb, &tmp_dir, &num_clu)) + return -EIO; + fid->size = (u64)num_clu << fsi->cluster_size_bits; + } + + /* FOR GRACEFUL ERROR HANDLING */ + if (IS_CLUS_FREE(fid->start_clu)) { + sdfat_fs_error(sb, + "non-zero size file starts with zero cluster " + "(size : %llu, p_dir : %u, entry : 0x%08x)", + fid->size, fid->dir.dir, fid->entry); + sdfat_debug_bug_on(1); + return -EIO; + } + + if (fsi->vol_type == EXFAT) + release_dentry_set(es); + } + + /* hint_stat will be used if this is directory. */ + fid->version = 0; + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + TMSG("%s exited successfully\n", __func__); + return 0; +} /* end of fscore_lookup */ + +/* create a file */ +s32 fscore_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid) +{ + s32 ret/*, dentry*/; + CHAIN_T dir; + UNI_NAME_T uni_name; + struct super_block *sb = inode->i_sb; + + /* check the validity of directory name in the given pathname */ + ret = resolve_path(inode, path, &dir, &uni_name); + if (ret) + return ret; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* create a new file */ + ret = create_file(inode, &dir, &uni_name, mode, fid); + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} + +/* read data from a opened file */ +s32 fscore_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount) +{ + s32 ret = 0; + s32 offset, sec_offset; + u32 clu_offset; + u32 clu; + u64 logsector, oneblkread, read_bytes; + struct buffer_head *tmp_bh = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* check if the given file ID is opened */ + if (fid->type != TYPE_FILE) + return -EPERM; + + if (fid->rwoffset > fid->size) + fid->rwoffset = fid->size; + + if (count > (fid->size - fid->rwoffset)) + count = fid->size - fid->rwoffset; + + if (count == 0) { + if (rcount) + *rcount = 0; + return 0; + } + + read_bytes = 0; + + while (count > 0) { + clu_offset = fid->rwoffset >> fsi->cluster_size_bits; + clu = fid->start_clu; + + if (fid->flags == 0x03) { + clu += clu_offset; + } else { + /* hint information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + clu = fid->hint_bmap.clu; + } + + while (clu_offset > 0) { + ret = get_next_clus_safe(sb, &clu); + if (ret) + goto err_out; + + clu_offset--; + } + } + + /* hint information */ + fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits; + fid->hint_bmap.clu = clu; + + offset = (s32)(fid->rwoffset & (fsi->cluster_size - 1)); /* byte offset in cluster */ + sec_offset = offset >> sb->s_blocksize_bits; /* sector offset in cluster */ + offset &= (sb->s_blocksize - 1); /* byte offset in sector */ + + logsector = CLUS_TO_SECT(fsi, clu) + sec_offset; + + oneblkread = (u64)(sb->s_blocksize - offset); + if (oneblkread > count) + oneblkread = count; + + if ((offset == 0) && (oneblkread == sb->s_blocksize)) { + ret = read_sect(sb, logsector, &tmp_bh, 1); + if (ret) + goto err_out; + memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data), (s32) oneblkread); + } else { + ret = read_sect(sb, logsector, &tmp_bh, 1); + if (ret) + goto err_out; + memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data)+offset, (s32) oneblkread); + } + count -= oneblkread; + read_bytes += oneblkread; + fid->rwoffset += oneblkread; + } + +err_out: + brelse(tmp_bh); + + /* set the size of read bytes */ + if (rcount != NULL) + *rcount = read_bytes; + + return ret; +} /* end of fscore_read_link */ + +/* write data into a opened file */ +s32 fscore_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount) +{ + s32 ret = 0; + s32 modified = false, offset, sec_offset; + u32 clu_offset, num_clusters, num_alloc; + u32 clu, last_clu; + u64 logsector, sector, oneblkwrite, write_bytes; + CHAIN_T new_clu; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + ENTRY_SET_CACHE_T *es = NULL; + struct buffer_head *tmp_bh = NULL; + struct super_block *sb = inode->i_sb; + u32 blksize = (u32)sb->s_blocksize; + u32 blksize_mask = (u32)(sb->s_blocksize-1); + u8 blksize_bits = sb->s_blocksize_bits; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* check if the given file ID is opened */ + if (fid->type != TYPE_FILE) + return -EPERM; + + if (fid->rwoffset > fid->size) + fid->rwoffset = fid->size; + + if (count == 0) { + if (wcount) + *wcount = 0; + return 0; + } + + fs_set_vol_flags(sb, VOL_DIRTY); + + if (fid->size == 0) + num_clusters = 0; + else + num_clusters = ((fid->size-1) >> fsi->cluster_size_bits) + 1; + + write_bytes = 0; + + while (count > 0) { + clu_offset = (fid->rwoffset >> fsi->cluster_size_bits); + clu = last_clu = fid->start_clu; + + if (fid->flags == 0x03) { + if ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) { + last_clu += clu_offset - 1; + + if (clu_offset == num_clusters) + clu = CLUS_EOF; + else + clu += clu_offset; + } + } else { + /* hint information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + clu = fid->hint_bmap.clu; + } + + while ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) { + last_clu = clu; + ret = get_next_clus_safe(sb, &clu); + if (ret) + goto err_out; + + clu_offset--; + } + } + + if (IS_CLUS_EOF(clu)) { + num_alloc = ((count-1) >> fsi->cluster_size_bits) + 1; + new_clu.dir = IS_CLUS_EOF(last_clu) ? CLUS_EOF : last_clu+1; + new_clu.size = 0; + new_clu.flags = fid->flags; + + /* (1) allocate a chain of clusters */ + ret = fsi->fs_func->alloc_cluster(sb, num_alloc, &new_clu, ALLOC_COLD); + if (ret) + goto err_out; + + /* (2) append to the FAT chain */ + if (IS_CLUS_EOF(last_clu)) { + if (new_clu.flags == 0x01) + fid->flags = 0x01; + fid->start_clu = new_clu.dir; + modified = true; + } else { + if (new_clu.flags != fid->flags) { + /* no-fat-chain bit is disabled, + * so fat-chain should be synced with + * alloc-bmp + */ + chain_cont_cluster(sb, fid->start_clu, num_clusters); + fid->flags = 0x01; + modified = true; + } + if (new_clu.flags == 0x01) { + ret = fat_ent_set(sb, last_clu, new_clu.dir); + if (ret) + goto err_out; + } + } + + num_clusters += num_alloc; + clu = new_clu.dir; + } + + /* hint information */ + fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits; + fid->hint_bmap.clu = clu; + + /* byte offset in cluster */ + offset = (s32)(fid->rwoffset & (fsi->cluster_size-1)); + /* sector offset in cluster */ + sec_offset = offset >> blksize_bits; + /* byte offset in sector */ + offset &= blksize_mask; + logsector = CLUS_TO_SECT(fsi, clu) + sec_offset; + + oneblkwrite = (u64)(blksize - offset); + if (oneblkwrite > count) + oneblkwrite = count; + + if ((offset == 0) && (oneblkwrite == blksize)) { + ret = read_sect(sb, logsector, &tmp_bh, 0); + if (ret) + goto err_out; + + memcpy(((s8 *)tmp_bh->b_data), + ((s8 *)buffer)+write_bytes, + (s32)oneblkwrite); + + ret = write_sect(sb, logsector, tmp_bh, 0); + if (ret) { + brelse(tmp_bh); + goto err_out; + } + } else { + if ((offset > 0) || ((fid->rwoffset+oneblkwrite) < fid->size)) { + ret = read_sect(sb, logsector, &tmp_bh, 1); + if (ret) + goto err_out; + } else { + ret = read_sect(sb, logsector, &tmp_bh, 0); + if (ret) + goto err_out; + } + + memcpy(((s8 *) tmp_bh->b_data)+offset, ((s8 *) buffer)+write_bytes, (s32) oneblkwrite); + ret = write_sect(sb, logsector, tmp_bh, 0); + if (ret) { + brelse(tmp_bh); + goto err_out; + } + } + + count -= oneblkwrite; + write_bytes += oneblkwrite; + fid->rwoffset += oneblkwrite; + + fid->attr |= ATTR_ARCHIVE; + + if (fid->size < fid->rwoffset) { + fid->size = fid->rwoffset; + modified = true; + } + } + + brelse(tmp_bh); + + /* (3) update the direcoty entry */ + /* get_entry_(set_)in_dir shoulb be check DIR_DELETED flag. */ + if (fsi->vol_type == EXFAT) { + es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) { + ret = -EIO; + goto err_out; + } + ep2 = ep+1; + } else { + ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §or); + if (!ep) { + ret = -EIO; + goto err_out; + } + ep2 = ep; + } + + fsi->fs_func->set_entry_time(ep, tm_now(SDFAT_SB(sb), &tm), TM_MODIFY); + fsi->fs_func->set_entry_attr(ep, fid->attr); + + if (modified) { + if (fsi->fs_func->get_entry_flag(ep2) != fid->flags) + fsi->fs_func->set_entry_flag(ep2, fid->flags); + + if (fsi->fs_func->get_entry_size(ep2) != fid->size) + fsi->fs_func->set_entry_size(ep2, fid->size); + + if (fsi->fs_func->get_entry_clu0(ep2) != fid->start_clu) + fsi->fs_func->set_entry_clu0(ep2, fid->start_clu); + } + + if (fsi->vol_type == EXFAT) { + if (update_dir_chksum_with_entry_set(sb, es)) { + ret = -EIO; + goto err_out; + } + release_dentry_set(es); + } else { + if (dcache_modify(sb, sector)) { + ret = -EIO; + goto err_out; + } + } + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + +err_out: + /* set the size of written bytes */ + if (wcount) + *wcount = write_bytes; + + return ret; +} /* end of fscore_write_link */ + +/* resize the file length */ +s32 fscore_truncate(struct inode *inode, u64 old_size, u64 new_size) +{ + u32 num_clusters_new, num_clusters_da, num_clusters_phys; + u32 last_clu = CLUS_FREE; + u64 sector; + CHAIN_T clu; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + ENTRY_SET_CACHE_T *es = NULL; + s32 evict = (fid->dir.dir == DIR_DELETED) ? 1 : 0; + + /* check if the given file ID is opened */ + if ((fid->type != TYPE_FILE) && (fid->type != TYPE_DIR)) + return -EPERM; + + /* TO CHECK inode type and size */ + MMSG("%s: inode(%p) type(%s) size:%lld->%lld\n", __func__, inode, + (fid->type == TYPE_FILE) ? "file" : "dir", old_size, new_size); + + /* XXX : This is for debugging. */ + + /* It can be when write failed */ +#if 0 + if (fid->size != old_size) { + DMSG("%s: inode(%p) size-mismatch(old:%lld != fid:%lld)\n", + __func__, inode, old_size, fid->size); + WARN_ON(1); + } +#endif + /* + * There is no lock to protect fid->size. + * So, we should get old_size and use it. + */ + if (old_size <= new_size) + return 0; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* Reserved count update */ + #define num_clusters(v) ((v) ? (u32)(((v) - 1) >> fsi->cluster_size_bits) + 1 : 0) + num_clusters_da = num_clusters(SDFAT_I(inode)->i_size_aligned); + num_clusters_new = num_clusters(i_size_read(inode)); + num_clusters_phys = num_clusters(SDFAT_I(inode)->i_size_ondisk); + + /* num_clusters(i_size_old) should be equal to num_clusters_da */ + BUG_ON((num_clusters(old_size)) != (num_clusters(SDFAT_I(inode)->i_size_aligned))); + + /* for debugging (FIXME: is okay on no-da case?) */ + BUG_ON(num_clusters_da < num_clusters_phys); + + if ((num_clusters_da != num_clusters_phys) && + (num_clusters_new < num_clusters_da)) { + /* Decrement reserved clusters + * n_reserved = num_clusters_da - max(new,phys) + */ + int n_reserved = (num_clusters_new > num_clusters_phys) ? + (num_clusters_da - num_clusters_new) : + (num_clusters_da - num_clusters_phys); + + fsi->reserved_clusters -= n_reserved; + BUG_ON(fsi->reserved_clusters < 0); + } + + clu.dir = fid->start_clu; + /* In no-da case, num_clusters_phys is equal to below value + * clu.size = (u32)((old_size-1) >> fsi->cluster_size_bits) + 1; + */ + clu.size = num_clusters_phys; + clu.flags = fid->flags; + + /* For bigdata */ + sdfat_statistics_set_trunc(clu.flags, &clu); + + if (new_size > 0) { + /* Truncate FAT chain num_clusters after the first cluster + * num_clusters = min(new, phys); + */ + u32 num_clusters = (num_clusters_new < num_clusters_phys) ? + num_clusters_new : num_clusters_phys; + + /* Follow FAT chain + * (defensive coding - works fine even with corrupted FAT table + */ + if (clu.flags == 0x03) { + clu.dir += num_clusters; + clu.size -= num_clusters; +#if 0 + /* extent_get_clus can`t know last_cluster + * when find target cluster in cache. + */ + } else if (fid->type == TYPE_FILE) { + u32 fclus = 0; + s32 err = extent_get_clus(inode, num_clusters, + &fclus, &(clu.dir), &last_clu, 0); + if (err) + return -EIO; + ASSERT(fclus == num_clusters); + + if ((num_clusters > 1) && (last_clu == fid->start_clu)) { + u32 fclus_tmp = 0; + u32 temp = 0; + + err = extent_get_clus(inode, num_clusters - 1, + &fclus_tmp, &last_clu, &temp, 0); + if (err) + return -EIO; + ASSERT(fclus_tmp == (num_clusters - 1)); + } + + num_clusters -= fclus; + clu.size -= fclus; +#endif + } else { + while (num_clusters > 0) { + last_clu = clu.dir; + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + + num_clusters--; + clu.size--; + } + } + + /* Optimization avialable: */ +#if 0 + if (num_clusters_new < num_clusters) { + < loop > + } else { + // num_clusters_new >= num_clusters_phys + // FAT truncation is not necessary + + clu.dir = CLUS_EOF; + clu.size = 0; + } +#endif + } else if (new_size == 0) { + fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; + fid->start_clu = CLUS_EOF; + } + fid->size = new_size; + + if (fid->type == TYPE_FILE) + fid->attr |= ATTR_ARCHIVE; + + /* + * clu.dir: free from + * clu.size: # of clusters to free (exFAT, 0x03 only), no fat_free if 0 + * clu.flags: fid->flags (exFAT only) + */ + + /* (1) update the directory entry */ + if (!evict) { + + if (fsi->vol_type == EXFAT) { + es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + } else { + ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §or); + if (!ep) + return -EIO; + ep2 = ep; + } + + fsi->fs_func->set_entry_time(ep, tm_now(SDFAT_SB(sb), &tm), TM_MODIFY); + fsi->fs_func->set_entry_attr(ep, fid->attr); + + /* + * if (fsi->vol_type != EXFAT) + * dcache_modify(sb, sector); + */ + + /* File size should be zero if there is no cluster allocated */ + if (IS_CLUS_EOF(fid->start_clu)) + fsi->fs_func->set_entry_size(ep2, 0); + else + fsi->fs_func->set_entry_size(ep2, new_size); + + if (new_size == 0) { + /* Any directory can not be truncated to zero */ + BUG_ON(fid->type != TYPE_FILE); + + fsi->fs_func->set_entry_flag(ep2, 0x01); + fsi->fs_func->set_entry_clu0(ep2, CLUS_FREE); + } + + if (fsi->vol_type == EXFAT) { + if (update_dir_chksum_with_entry_set(sb, es)) + return -EIO; + release_dentry_set(es); + } else { + if (dcache_modify(sb, sector)) + return -EIO; + } + + } /* end of if(fid->dir.dir != DIR_DELETED) */ + + /* (2) cut off from the FAT chain */ + if ((fid->flags == 0x01) && + (!IS_CLUS_FREE(last_clu)) && (!IS_CLUS_EOF(last_clu))) { + if (fat_ent_set(sb, last_clu, CLUS_EOF)) + return -EIO; + } + + /* (3) invalidate cache and free the clusters */ + /* clear extent cache */ + extent_cache_inval_inode(inode); + + /* hint information */ + fid->hint_bmap.off = CLUS_EOF; + fid->hint_bmap.clu = CLUS_EOF; + if (fid->rwoffset > fid->size) + fid->rwoffset = fid->size; + + /* hint_stat will be used if this is directory. */ + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + /* free the clusters */ + if (fsi->fs_func->free_cluster(sb, &clu, evict)) + return -EIO; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return 0; +} /* end of fscore_truncate */ + +static void update_parent_info(FILE_ID_T *fid, struct inode *parent_inode) +{ + FS_INFO_T *fsi = &(SDFAT_SB(parent_inode->i_sb)->fsi); + FILE_ID_T *parent_fid = &(SDFAT_I(parent_inode)->fid); + + /* + * the problem that FILE_ID_T caches wrong parent info. + * + * because of flag-mismatch of fid->dir, + * there is abnormal traversing cluster chain. + */ + if (unlikely((parent_fid->flags != fid->dir.flags) + || (parent_fid->size != (fid->dir.size<cluster_size_bits)) + || (parent_fid->start_clu != fid->dir.dir))) { + + fid->dir.dir = parent_fid->start_clu; + fid->dir.flags = parent_fid->flags; + fid->dir.size = ((parent_fid->size + (fsi->cluster_size-1)) + >> fsi->cluster_size_bits); + } +} + +/* rename or move a old file into a new file */ +s32 fscore_rename(struct inode *old_parent_inode, FILE_ID_T *fid, + struct inode *new_parent_inode, struct dentry *new_dentry) +{ + s32 ret; + s32 dentry; + CHAIN_T olddir, newdir; + CHAIN_T *p_dir = NULL; + UNI_NAME_T uni_name; + DENTRY_T *ep; + struct super_block *sb = old_parent_inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u8 *new_path = (u8 *) new_dentry->d_name.name; + struct inode *new_inode = new_dentry->d_inode; + int num_entries; + FILE_ID_T *new_fid = NULL; + u32 new_entry_type = TYPE_UNUSED; + s32 new_entry = 0; + + /* check the validity of pointer parameters */ + if ((new_path == NULL) || (strlen(new_path) == 0)) + return -EINVAL; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted source dentry\n", __func__); + return -ENOENT; + } + + /* patch 1.2.4 : the problem that FILE_ID_T caches wrong parent info. */ + update_parent_info(fid, old_parent_inode); + + olddir.dir = fid->dir.dir; + olddir.size = fid->dir.size; + olddir.flags = fid->dir.flags; + + dentry = fid->entry; + + /* check if the old file is "." or ".." */ + if (fsi->vol_type != EXFAT) { + if ((olddir.dir != fsi->root_dir) && (dentry < 2)) + return -EPERM; + } + + ep = get_dentry_in_dir(sb, &olddir, dentry, NULL); + if (!ep) + return -EIO; + +#ifdef CONFIG_SDFAT_CHECK_RO_ATTR + if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; +#endif + + /* check whether new dir is existing directory and empty */ + if (new_inode) { + ret = -EIO; + new_fid = &SDFAT_I(new_inode)->fid; + + if (new_fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted target dentry\n", __func__); + goto out; + } + + /* patch 1.2.4 : + * the problem that FILE_ID_T caches wrong parent info. + * + * FIXME : is needed? + */ + update_parent_info(new_fid, new_parent_inode); + + p_dir = &(new_fid->dir); + new_entry = new_fid->entry; + ep = get_dentry_in_dir(sb, p_dir, new_entry, NULL); + if (!ep) + goto out; + + new_entry_type = fsi->fs_func->get_entry_type(ep); + + /* if new_inode exists, update fid */ + new_fid->size = i_size_read(new_inode); + + if (new_entry_type == TYPE_DIR) { + CHAIN_T new_clu; + + new_clu.dir = new_fid->start_clu; + new_clu.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1; + new_clu.flags = new_fid->flags; + + ret = check_dir_empty(sb, &new_clu); + if (ret) + return ret; + } + } + + /* check the validity of directory name in the given new pathname */ + ret = resolve_path(new_parent_inode, new_path, &newdir, &uni_name); + if (ret) + return ret; + + fs_set_vol_flags(sb, VOL_DIRTY); + + if (olddir.dir == newdir.dir) + ret = rename_file(new_parent_inode, &olddir, dentry, &uni_name, fid); + else + ret = move_file(new_parent_inode, &olddir, dentry, &newdir, &uni_name, fid); + + if ((!ret) && new_inode) { + /* delete entries of new_dir */ + ep = get_dentry_in_dir(sb, p_dir, new_entry, NULL); + if (!ep) { + ret = -EIO; + goto del_out; + } + + num_entries = fsi->fs_func->count_ext_entries(sb, p_dir, new_entry, ep); + if (num_entries < 0) { + ret = -EIO; + goto del_out; + } + + + if (fsi->fs_func->delete_dir_entry(sb, p_dir, new_entry, 0, num_entries+1)) { + ret = -EIO; + goto del_out; + } + + /* Free the clusters if new_inode is a dir(as if fscore_rmdir) */ + if (new_entry_type == TYPE_DIR) { + /* new_fid, new_clu_to_free */ + CHAIN_T new_clu_to_free; + + new_clu_to_free.dir = new_fid->start_clu; + new_clu_to_free.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1; + new_clu_to_free.flags = new_fid->flags; + + if (fsi->fs_func->free_cluster(sb, &new_clu_to_free, 1)) { + /* just set I/O error only */ + ret = -EIO; + } + + new_fid->size = 0; + new_fid->start_clu = CLUS_EOF; + new_fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; + } +del_out: + /* Update new_inode fid + * Prevent syncing removed new_inode + * (new_fid is already initialized above code ("if (new_inode)") + */ + new_fid->dir.dir = DIR_DELETED; + } +out: + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} /* end of fscore_rename */ + +/* remove a file */ +s32 fscore_remove(struct inode *inode, FILE_ID_T *fid) +{ + s32 ret; + s32 dentry; + CHAIN_T dir, clu_to_free; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + dir.dir = fid->dir.dir; + dir.size = fid->dir.size; + dir.flags = fid->dir.flags; + + dentry = fid->entry; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + return -ENOENT; + } + + ep = get_dentry_in_dir(sb, &dir, dentry, NULL); + if (!ep) + return -EIO; + + +#ifdef CONFIG_SDFAT_CHECK_RO_ATTR + if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; +#endif + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* (1) update the directory entry */ + ret = remove_file(inode, &dir, dentry); + if (ret) + goto out; + + clu_to_free.dir = fid->start_clu; + clu_to_free.size = ((fid->size-1) >> fsi->cluster_size_bits) + 1; + clu_to_free.flags = fid->flags; + + /* (2) invalidate extent cache and free the clusters + */ + /* clear extent cache */ + extent_cache_inval_inode(inode); + ret = fsi->fs_func->free_cluster(sb, &clu_to_free, 0); + /* WARN : DO NOT RETURN ERROR IN HERE */ + + /* (3) update FILE_ID_T */ + fid->size = 0; + fid->start_clu = CLUS_EOF; + fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; + fid->dir.dir = DIR_DELETED; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); +out: + return ret; +} /* end of fscore_remove */ + + +/* + * Get the information of a given file + * REMARK : This function does not need any file name on linux + * + * info.Size means the value saved on disk. + * But root directory doesn`t have real dentry, + * so the size of root directory returns calculated one exceptively. + */ +s32 fscore_read_inode(struct inode *inode, DIR_ENTRY_T *info) +{ + u64 sector; + s32 count; + CHAIN_T dir; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + ENTRY_SET_CACHE_T *es = NULL; + u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0; + + TMSG("%s entered\n", __func__); + + extent_cache_init_inode(inode); + + /* if root directory */ + if (is_dir && (fid->dir.dir == fsi->root_dir) && (fid->entry == -1)) { + info->Attr = ATTR_SUBDIR; + memset((s8 *) &info->CreateTimestamp, 0, sizeof(DATE_TIME_T)); + memset((s8 *) &info->ModifyTimestamp, 0, sizeof(DATE_TIME_T)); + memset((s8 *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T)); + //strcpy(info->NameBuf.sfn, "."); + //strcpy(info->NameBuf.lfn, "."); + + dir.dir = fsi->root_dir; + dir.flags = 0x01; + dir.size = 0; /* UNUSED */ + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(fsi->root_dir)) { + info->Size = fsi->dentries_in_root << DENTRY_SIZE_BITS; + } else { + u32 num_clu; + + if (__count_num_clusters(sb, &dir, &num_clu)) + return -EIO; + info->Size = (u64)num_clu << fsi->cluster_size_bits; + } + + count = __count_dos_name_entries(sb, &dir, TYPE_DIR, NULL); + if (count < 0) + return -EIO; + info->NumSubdirs = count; + + return 0; + } + + /* get the directory entry of given file or directory */ + if (fsi->vol_type == EXFAT) { + /* es should be released */ + es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_2_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + } else { + ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §or); + if (!ep) + return -EIO; + ep2 = ep; + /* dcache should be unlocked */ + dcache_lock(sb, sector); + } + + /* set FILE_INFO structure using the acquired DENTRY_T */ + info->Attr = fsi->fs_func->get_entry_attr(ep); + + fsi->fs_func->get_entry_time(ep, &tm, TM_CREATE); + info->CreateTimestamp.Year = tm.year; + info->CreateTimestamp.Month = tm.mon; + info->CreateTimestamp.Day = tm.day; + info->CreateTimestamp.Hour = tm.hour; + info->CreateTimestamp.Minute = tm.min; + info->CreateTimestamp.Second = tm.sec; + info->CreateTimestamp.MilliSecond = 0; + + fsi->fs_func->get_entry_time(ep, &tm, TM_MODIFY); + info->ModifyTimestamp.Year = tm.year; + info->ModifyTimestamp.Month = tm.mon; + info->ModifyTimestamp.Day = tm.day; + info->ModifyTimestamp.Hour = tm.hour; + info->ModifyTimestamp.Minute = tm.min; + info->ModifyTimestamp.Second = tm.sec; + info->ModifyTimestamp.MilliSecond = 0; + + memset((s8 *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T)); + + info->NumSubdirs = 0; + info->Size = fsi->fs_func->get_entry_size(ep2); + + if (fsi->vol_type == EXFAT) + release_dentry_set(es); + else + dcache_unlock(sb, sector); + + if (is_dir) { + u32 dotcnt = 0; + + dir.dir = fid->start_clu; + dir.flags = fid->flags; + dir.size = fid->size >> fsi->cluster_size_bits; + /* + * NOTE : + * If "dir.flags" has 0x01, "dir.size" is meaningless. + */ +#if 0 + if (info->Size == 0) { + s32 num_clu; + + if (__count_num_clusters(sb, &dir, &num_clu)) + return -EIO; + info->Size = (u64)num_clu << fsi->cluster_size_bits; + } +#endif + count = __count_dos_name_entries(sb, &dir, TYPE_DIR, &dotcnt); + if (count < 0) + return -EIO; + + if (fsi->vol_type == EXFAT) { + count += SDFAT_MIN_SUBDIR; + } else { + /* + * if directory has been corrupted, + * we have to adjust subdir count. + */ + BUG_ON(dotcnt > SDFAT_MIN_SUBDIR); + if (dotcnt < SDFAT_MIN_SUBDIR) { + EMSG("%s: contents of the directory has been " + "corrupted (parent clus : %08x, idx : %d)", + __func__, fid->dir.dir, fid->entry); + } + count += (SDFAT_MIN_SUBDIR - dotcnt); + } + info->NumSubdirs = count; + } + + TMSG("%s exited successfully\n", __func__); + return 0; +} /* end of fscore_read_inode */ + +/* set the information of a given file + * REMARK : This function does not need any file name on linux + */ +s32 fscore_write_inode(struct inode *inode, DIR_ENTRY_T *info, s32 sync) +{ + s32 ret = -EIO; + u64 sector; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + ENTRY_SET_CACHE_T *es = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0; + + + /* SKIP WRITING INODE : + * if the indoe is already unlinked, + * there is no need for updating inode + */ + if (fid->dir.dir == DIR_DELETED) + return 0; + + if (is_dir && (fid->dir.dir == fsi->root_dir) && (fid->entry == -1)) + return 0; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* get the directory entry of given file or directory */ + if (fsi->vol_type == EXFAT) { + es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + } else { + /* for other than exfat */ + ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §or); + if (!ep) + return -EIO; + ep2 = ep; + } + + + fsi->fs_func->set_entry_attr(ep, info->Attr); + + /* set FILE_INFO structure using the acquired DENTRY_T */ + tm.sec = info->CreateTimestamp.Second; + tm.min = info->CreateTimestamp.Minute; + tm.hour = info->CreateTimestamp.Hour; + tm.day = info->CreateTimestamp.Day; + tm.mon = info->CreateTimestamp.Month; + tm.year = info->CreateTimestamp.Year; + fsi->fs_func->set_entry_time(ep, &tm, TM_CREATE); + + tm.sec = info->ModifyTimestamp.Second; + tm.min = info->ModifyTimestamp.Minute; + tm.hour = info->ModifyTimestamp.Hour; + tm.day = info->ModifyTimestamp.Day; + tm.mon = info->ModifyTimestamp.Month; + tm.year = info->ModifyTimestamp.Year; + fsi->fs_func->set_entry_time(ep, &tm, TM_MODIFY); + + if (is_dir && fsi->vol_type != EXFAT) { + /* overwirte dirsize if FAT32 and dir size != 0 */ + if (fsi->fs_func->get_entry_size(ep2)) + fsi->fs_func->set_entry_size(ep2, 0); + } else { + /* File size should be zero if there is no cluster allocated */ + u64 on_disk_size = info->Size; + + if (IS_CLUS_EOF(fid->start_clu)) + on_disk_size = 0; + + fsi->fs_func->set_entry_size(ep2, on_disk_size); + } + + if (fsi->vol_type == EXFAT) { + ret = update_dir_chksum_with_entry_set(sb, es); + release_dentry_set(es); + } else { + ret = dcache_modify(sb, sector); + } + + fs_sync(sb, sync); + /* Comment below code to prevent super block update frequently */ + //fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} /* end of fscore_write_inode */ + + +/* + * Input: inode, (logical) clu_offset, target allocation area + * Output: errcode, cluster number + * *clu = (~0), if it's unable to allocate a new cluster + */ +s32 fscore_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest) +{ + s32 ret, modified = false; + u32 last_clu; + u64 sector; + CHAIN_T new_clu; + DENTRY_T *ep; + ENTRY_SET_CACHE_T *es = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + u32 local_clu_offset = clu_offset; + s32 reserved_clusters = fsi->reserved_clusters; + u32 num_to_be_allocated = 0, num_clusters = 0; + + fid->rwoffset = (s64)(clu_offset) << fsi->cluster_size_bits; + + if (SDFAT_I(inode)->i_size_ondisk > 0) + num_clusters = (u32)((SDFAT_I(inode)->i_size_ondisk-1) >> fsi->cluster_size_bits) + 1; + + if (clu_offset >= num_clusters) + num_to_be_allocated = clu_offset - num_clusters + 1; + + if ((dest == ALLOC_NOWHERE) && (num_to_be_allocated > 0)) { + *clu = CLUS_EOF; + return 0; + } + + /* check always request cluster is 1 */ + //ASSERT(num_to_be_allocated == 1); + + sdfat_debug_check_clusters(inode); + + *clu = last_clu = fid->start_clu; + + /* XXX: Defensive code needed. + * what if i_size_ondisk != # of allocated clusters + */ + if (fid->flags == 0x03) { + if ((clu_offset > 0) && (!IS_CLUS_EOF(*clu))) { + last_clu += clu_offset - 1; + + if (clu_offset == num_clusters) + *clu = CLUS_EOF; + else + *clu += clu_offset; + } + } else if (fid->type == TYPE_FILE) { + u32 fclus = 0; + s32 err = extent_get_clus(inode, clu_offset, + &fclus, clu, &last_clu, 1); + if (err) + return -EIO; + + clu_offset -= fclus; + } else { + /* hint information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + /* hint_bmap.clu should be valid */ + ASSERT(fid->hint_bmap.clu >= 2); + *clu = fid->hint_bmap.clu; + } + + while ((clu_offset > 0) && (!IS_CLUS_EOF(*clu))) { + last_clu = *clu; + if (get_next_clus_safe(sb, clu)) + return -EIO; + clu_offset--; + } + } + + if (IS_CLUS_EOF(*clu)) { + fs_set_vol_flags(sb, VOL_DIRTY); + + new_clu.dir = (IS_CLUS_EOF(last_clu)) ? CLUS_EOF : last_clu + 1; + new_clu.size = 0; + new_clu.flags = fid->flags; + + /* (1) allocate a cluster */ + if (num_to_be_allocated < 1) { + /* Broken FAT (i_sze > allocated FAT) */ + EMSG("%s: invalid fat chain : inode(%p) " + "num_to_be_allocated(%d) " + "i_size_ondisk(%lld) fid->flags(%02x) " + "fid->start(%08x) fid->hint_off(%u) " + "fid->hint_clu(%u) fid->rwoffset(%llu) " + "modified_clu_off(%d) last_clu(%08x) " + "new_clu(%08x)", __func__, inode, + num_to_be_allocated, + (SDFAT_I(inode)->i_size_ondisk), + fid->flags, fid->start_clu, + fid->hint_bmap.off, fid->hint_bmap.clu, + fid->rwoffset, clu_offset, + last_clu, new_clu.dir); + sdfat_fs_error(sb, "broken FAT chain."); + return -EIO; + } + + ret = fsi->fs_func->alloc_cluster(sb, num_to_be_allocated, &new_clu, ALLOC_COLD); + if (ret) + return ret; + + if (IS_CLUS_EOF(new_clu.dir) || IS_CLUS_FREE(new_clu.dir)) { + sdfat_fs_error(sb, "bogus cluster new allocated" + "(last_clu : %u, new_clu : %u)", + last_clu, new_clu.dir); + ASSERT(0); + return -EIO; + } + + /* Reserved cluster dec. */ + // XXX: Inode DA flag needed + if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_DELAY) { + BUG_ON(reserved_clusters < num_to_be_allocated); + reserved_clusters -= num_to_be_allocated; + + } + + /* (2) append to the FAT chain */ + if (IS_CLUS_EOF(last_clu)) { + if (new_clu.flags == 0x01) + fid->flags = 0x01; + fid->start_clu = new_clu.dir; + modified = true; + } else { + if (new_clu.flags != fid->flags) { + /* no-fat-chain bit is disabled, + * so fat-chain should be synced with alloc-bmp + */ + chain_cont_cluster(sb, fid->start_clu, num_clusters); + fid->flags = 0x01; + modified = true; + } + if (new_clu.flags == 0x01) + if (fat_ent_set(sb, last_clu, new_clu.dir)) + return -EIO; + } + + num_clusters += num_to_be_allocated; + *clu = new_clu.dir; + + if (fid->dir.dir != DIR_DELETED) { + + if (fsi->vol_type == EXFAT) { + es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) + return -EIO; + /* get stream entry */ + ep++; + } + + /* (3) update directory entry */ + if (modified) { + if (fsi->vol_type != EXFAT) { + ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §or); + if (!ep) + return -EIO; + } + + if (fsi->fs_func->get_entry_flag(ep) != fid->flags) + fsi->fs_func->set_entry_flag(ep, fid->flags); + + if (fsi->fs_func->get_entry_clu0(ep) != fid->start_clu) + fsi->fs_func->set_entry_clu0(ep, fid->start_clu); + + fsi->fs_func->set_entry_size(ep, fid->size); + + if (fsi->vol_type != EXFAT) { + if (dcache_modify(sb, sector)) + return -EIO; + } + } + + if (fsi->vol_type == EXFAT) { + if (update_dir_chksum_with_entry_set(sb, es)) + return -EIO; + release_dentry_set(es); + } + + } /* end of if != DIR_DELETED */ + + + /* add number of new blocks to inode (non-DA only) */ + if (!(SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_DELAY)) { + inode->i_blocks += num_to_be_allocated << (fsi->cluster_size_bits - sb->s_blocksize_bits); + } else { + // DA의 경우, i_blocks가 이미 증가해있어야 함. + BUG_ON(clu_offset >= (inode->i_blocks >> (fsi->cluster_size_bits - sb->s_blocksize_bits))); + } +#if 0 + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); +#endif + /* (4) Move *clu pointer along FAT chains (hole care) + * because the caller of this function expect *clu to be the last cluster. + * This only works when num_to_be_allocated >= 2, + * *clu = (the first cluster of the allocated chain) => (the last cluster of ...) + */ + if (fid->flags == 0x03) { + *clu += num_to_be_allocated - 1; + } else { + while (num_to_be_allocated > 1) { + if (get_next_clus_safe(sb, clu)) + return -EIO; + num_to_be_allocated--; + } + } + + } + + /* update reserved_clusters */ + fsi->reserved_clusters = reserved_clusters; + + /* hint information */ + fid->hint_bmap.off = local_clu_offset; + fid->hint_bmap.clu = *clu; + + return 0; +} /* end of fscore_map_clus */ + +/* allocate reserved cluster */ +s32 fscore_reserve_clus(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - 2)) + return -ENOSPC; + + if (bdev_check_bdi_valid(sb)) + return -EIO; + + fsi->reserved_clusters++; + + /* inode->i_blocks update */ + inode->i_blocks += 1 << (fsi->cluster_size_bits - sb->s_blocksize_bits); + + sdfat_debug_check_clusters(inode); + + return 0; +} + +/* remove an entry, BUT don't truncate */ +s32 fscore_unlink(struct inode *inode, FILE_ID_T *fid) +{ + s32 dentry; + CHAIN_T dir; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + + dir.dir = fid->dir.dir; + dir.size = fid->dir.size; + dir.flags = fid->dir.flags; + + dentry = fid->entry; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + return -ENOENT; + } + + ep = get_dentry_in_dir(sb, &dir, dentry, NULL); + if (!ep) + return -EIO; + +#ifdef CONFIG_SDFAT_CHECK_RO_ATTR + if (SDFAT_SB(sb)->fsi.fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; +#endif + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* (1) update the directory entry */ + if (remove_file(inode, &dir, dentry)) + return -EIO; + + /* This doesn't modify fid */ + fid->dir.dir = DIR_DELETED; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return 0; +} + +/*----------------------------------------------------------------------*/ +/* Directory Operation Functions */ +/*----------------------------------------------------------------------*/ + +/* create a directory */ +s32 fscore_mkdir(struct inode *inode, u8 *path, FILE_ID_T *fid) +{ + s32 ret/*, dentry*/; + CHAIN_T dir; + UNI_NAME_T uni_name; + struct super_block *sb = inode->i_sb; + + TMSG("%s entered\n", __func__); + + /* check the validity of directory name in the given old pathname */ + ret = resolve_path(inode, path, &dir, &uni_name); + if (ret) + goto out; + + fs_set_vol_flags(sb, VOL_DIRTY); + + ret = create_dir(inode, &dir, &uni_name, fid); + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); +out: + TMSG("%s exited with err(%d)\n", __func__, ret); + return ret; +} + +/* read a directory entry from the opened directory */ +s32 fscore_readdir(struct inode *inode, DIR_ENTRY_T *dir_entry) +{ + s32 i; + s32 dentries_per_clu, dentries_per_clu_bits = 0; + u32 type, clu_offset; + u64 sector; + CHAIN_T dir, clu; + UNI_NAME_T uni_name; + TIMESTAMP_T tm; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + u32 dentry = (u32)(fid->rwoffset & 0xFFFFFFFF); /* u32 is enough for directory */ + + /* check if the given file ID is opened */ + if (fid->type != TYPE_DIR) + return -EPERM; + + if (fid->entry == -1) { + dir.dir = fsi->root_dir; + dir.size = 0; /* just initialize, but will not use */ + dir.flags = 0x01; + } else { + dir.dir = fid->start_clu; + dir.size = fid->size >> fsi->cluster_size_bits; + dir.flags = fid->flags; + sdfat_debug_bug_on(dentry >= (dir.size * fsi->dentries_per_clu)); + } + + if (IS_CLUS_FREE(dir.dir)) { /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + + /* Prevent readdir over directory size */ + if (dentry >= dentries_per_clu) { + clu.dir = CLUS_EOF; + } else { + clu.dir = dir.dir; + clu.size = dir.size; + clu.flags = dir.flags; + } + } else { + dentries_per_clu = fsi->dentries_per_clu; + dentries_per_clu_bits = ilog2(dentries_per_clu); + + clu_offset = dentry >> dentries_per_clu_bits; + clu.dir = dir.dir; + clu.size = dir.size; + clu.flags = dir.flags; + + if (clu.flags == 0x03) { + clu.dir += clu_offset; + clu.size -= clu_offset; + } else { + /* hint_information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + clu.dir = fid->hint_bmap.clu; + } + + while (clu_offset > 0) { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + + clu_offset--; + } + } + } + + while (!IS_CLUS_EOF(clu.dir)) { + if (IS_CLUS_FREE(dir.dir)) /* FAT16 root_dir */ + i = dentry % dentries_per_clu; + else + i = dentry & (dentries_per_clu-1); + + for ( ; i < dentries_per_clu; i++, dentry++) { + ep = get_dentry_in_dir(sb, &clu, i, §or); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type(ep); + + if (type == TYPE_UNUSED) + break; + + if ((type != TYPE_FILE) && (type != TYPE_DIR)) + continue; + + dcache_lock(sb, sector); + dir_entry->Attr = fsi->fs_func->get_entry_attr(ep); + + fsi->fs_func->get_entry_time(ep, &tm, TM_CREATE); + dir_entry->CreateTimestamp.Year = tm.year; + dir_entry->CreateTimestamp.Month = tm.mon; + dir_entry->CreateTimestamp.Day = tm.day; + dir_entry->CreateTimestamp.Hour = tm.hour; + dir_entry->CreateTimestamp.Minute = tm.min; + dir_entry->CreateTimestamp.Second = tm.sec; + dir_entry->CreateTimestamp.MilliSecond = 0; + + fsi->fs_func->get_entry_time(ep, &tm, TM_MODIFY); + dir_entry->ModifyTimestamp.Year = tm.year; + dir_entry->ModifyTimestamp.Month = tm.mon; + dir_entry->ModifyTimestamp.Day = tm.day; + dir_entry->ModifyTimestamp.Hour = tm.hour; + dir_entry->ModifyTimestamp.Minute = tm.min; + dir_entry->ModifyTimestamp.Second = tm.sec; + dir_entry->ModifyTimestamp.MilliSecond = 0; + + memset((s8 *) &dir_entry->AccessTimestamp, 0, sizeof(DATE_TIME_T)); + + *(uni_name.name) = 0x0; + fsi->fs_func->get_uniname_from_ext_entry(sb, &dir, dentry, uni_name.name); + if (*(uni_name.name) == 0x0) + get_uniname_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x1); + nls_uni16s_to_vfsname(sb, &uni_name, + dir_entry->NameBuf.lfn, + dir_entry->NameBuf.lfnbuf_len); + dcache_unlock(sb, sector); + + if (fsi->vol_type == EXFAT) { + ep = get_dentry_in_dir(sb, &clu, i+1, NULL); + if (!ep) + return -EIO; + } else { + get_uniname_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x0); + nls_uni16s_to_vfsname(sb, &uni_name, + dir_entry->NameBuf.sfn, + dir_entry->NameBuf.sfnbuf_len); + } + + dir_entry->Size = fsi->fs_func->get_entry_size(ep); + + /* + * Update hint information : + * fat16 root directory does not need it. + */ + if (!IS_CLUS_FREE(dir.dir)) { + fid->hint_bmap.off = dentry >> dentries_per_clu_bits; + fid->hint_bmap.clu = clu.dir; + } + + fid->rwoffset = (s64) ++dentry; + + return 0; + } + + /* fat16 root directory */ + if (IS_CLUS_FREE(dir.dir)) + break; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + } + + dir_entry->NameBuf.lfn[0] = '\0'; + + fid->rwoffset = (s64)dentry; + + return 0; +} /* end of fscore_readdir */ + +/* remove a directory */ +s32 fscore_rmdir(struct inode *inode, FILE_ID_T *fid) +{ + s32 ret; + s32 dentry; + DENTRY_T *ep; + CHAIN_T dir, clu_to_free; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + dir.dir = fid->dir.dir; + dir.size = fid->dir.size; + dir.flags = fid->dir.flags; + + dentry = fid->entry; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + return -ENOENT; + } + + /* check if the file is "." or ".." */ + if (fsi->vol_type != EXFAT) { + if ((dir.dir != fsi->root_dir) && (dentry < 2)) + return -EPERM; + } + + ep = get_dentry_in_dir(sb, &dir, dentry, NULL); + if (!ep) + return -EIO; + +#ifdef CONFIG_SDFAT_CHECK_RO_ATTR + if (SDFAT_SB(sb)->fsi.fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; +#endif + + clu_to_free.dir = fid->start_clu; + clu_to_free.size = ((fid->size-1) >> fsi->cluster_size_bits) + 1; + clu_to_free.flags = fid->flags; + + ret = check_dir_empty(sb, &clu_to_free); + if (ret) { + if (ret == -EIO) + EMSG("%s : failed to check_dir_empty : err(%d)\n", + __func__, ret); + return ret; + } + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* (1) update the directory entry */ + ret = remove_file(inode, &dir, dentry); + if (ret) { + EMSG("%s : failed to remove_file : err(%d)\n", __func__, ret); + return ret; + } + + fid->dir.dir = DIR_DELETED; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} /* end of fscore_rmdir */ + +/* end of core.c */ diff --git a/fs/sdfat/core.h b/fs/sdfat/core.h new file mode 100644 index 0000000000000..00ea2c5e26032 --- /dev/null +++ b/fs/sdfat/core.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _SDFAT_CORE_H +#define _SDFAT_CORE_H + +#include + +#include "config.h" +#include "api.h" +#include "upcase.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ +#define get_next_clus(sb, pclu) fat_ent_get(sb, *(pclu), pclu) +#define get_next_clus_safe(sb, pclu) fat_ent_get_safe(sb, *(pclu), pclu) + +/* file status */ +/* this prevents + * fscore_write_inode, fscore_map_clus, ... with the unlinked inodes + * from corrupting on-disk dentry data. + * + * The fid->dir value of unlinked inode will be DIR_DELETED + * and those functions must check if fid->dir is valid prior to + * the calling of get_dentry_in_dir() + */ +#define DIR_DELETED 0xFFFF0321 + +/*----------------------------------------------------------------------*/ +/* Type Definitions */ +/*----------------------------------------------------------------------*/ +#define ES_2_ENTRIES 2 +#define ES_3_ENTRIES 3 +#define ES_ALL_ENTRIES 0 + +typedef struct { + u64 sector; // sector number that contains file_entry + u32 offset; // byte offset in the sector + s32 alloc_flag; // flag in stream entry. 01 for cluster chain, 03 for contig. clusters. + u32 num_entries; + void *__buf; // __buf should be the last member +} ENTRY_SET_CACHE_T; + + + +/*----------------------------------------------------------------------*/ +/* External Function Declarations */ +/*----------------------------------------------------------------------*/ + +/* file system initialization & shutdown functions */ +s32 fscore_init(void); +s32 fscore_shutdown(void); + +/* chain management */ +s32 chain_cont_cluster(struct super_block *sb, u32 chain, u32 len); + +/* volume management functions */ +s32 fscore_mount(struct super_block *sb); +s32 fscore_umount(struct super_block *sb); +s32 fscore_statfs(struct super_block *sb, VOL_INFO_T *info); +s32 fscore_sync_fs(struct super_block *sb, s32 do_sync); +s32 fscore_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync); +u32 fscore_get_au_stat(struct super_block *sb, s32 mode); + +/* file management functions */ +s32 fscore_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid); +s32 fscore_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid); +s32 fscore_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount); +s32 fscore_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount); +s32 fscore_truncate(struct inode *inode, u64 old_size, u64 new_size); +s32 fscore_rename(struct inode *old_parent_inode, FILE_ID_T *fid, + struct inode *new_parent_inode, struct dentry *new_dentry); +s32 fscore_remove(struct inode *inode, FILE_ID_T *fid); +s32 fscore_read_inode(struct inode *inode, DIR_ENTRY_T *info); +s32 fscore_write_inode(struct inode *inode, DIR_ENTRY_T *info, int sync); +s32 fscore_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest); +s32 fscore_reserve_clus(struct inode *inode); +s32 fscore_unlink(struct inode *inode, FILE_ID_T *fid); + +/* directory management functions */ +s32 fscore_mkdir(struct inode *inode, u8 *path, FILE_ID_T *fid); +s32 fscore_readdir(struct inode *inode, DIR_ENTRY_T *dir_ent); +s32 fscore_rmdir(struct inode *inode, FILE_ID_T *fid); + + +/*----------------------------------------------------------------------*/ +/* External Function Declarations (NOT TO UPPER LAYER) */ +/*----------------------------------------------------------------------*/ + +/* core.c : core code for common */ +/* dir entry management functions */ +DENTRY_T *get_dentry_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector); + +/* name conversion functions */ +void get_uniname_from_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, UNI_NAME_T *p_uniname, u8 mode); + +/* file operation functions */ +s32 walk_fat_chain(struct super_block *sb, CHAIN_T *p_dir, u32 byte_offset, u32 *clu); + +/* sdfat/cache.c */ +s32 meta_cache_init(struct super_block *sb); +s32 meta_cache_shutdown(struct super_block *sb); +u8 *fcache_getblk(struct super_block *sb, u64 sec); +s32 fcache_modify(struct super_block *sb, u64 sec); +s32 fcache_release_all(struct super_block *sb); +s32 fcache_flush(struct super_block *sb, u32 sync); + +u8 *dcache_getblk(struct super_block *sb, u64 sec); +s32 dcache_modify(struct super_block *sb, u64 sec); +s32 dcache_lock(struct super_block *sb, u64 sec); +s32 dcache_unlock(struct super_block *sb, u64 sec); +s32 dcache_release(struct super_block *sb, u64 sec); +s32 dcache_release_all(struct super_block *sb); +s32 dcache_flush(struct super_block *sb, u32 sync); +s32 dcache_readahead(struct super_block *sb, u64 sec); + + +/* fatent.c */ +s32 fat_ent_ops_init(struct super_block *sb); +s32 fat_ent_get(struct super_block *sb, u32 loc, u32 *content); +s32 fat_ent_set(struct super_block *sb, u32 loc, u32 content); +s32 fat_ent_get_safe(struct super_block *sb, u32 loc, u32 *content); + +/* core_fat.c : core code for fat */ +s32 fat_generate_dos_name_new(struct super_block *sb, CHAIN_T *p_dir, DOS_NAME_T *p_dosname, s32 n_entries); +s32 mount_fat16(struct super_block *sb, pbr_t *p_pbr); +s32 mount_fat32(struct super_block *sb, pbr_t *p_pbr); + +/* core_exfat.c : core code for exfat */ + +s32 load_alloc_bmp(struct super_block *sb); +void free_alloc_bmp(struct super_block *sb); +ENTRY_SET_CACHE_T *get_dentry_set_in_dir(struct super_block *sb, + CHAIN_T *p_dir, s32 entry, u32 type, DENTRY_T **file_ep); +void release_dentry_set(ENTRY_SET_CACHE_T *es); +s32 update_dir_chksum(struct super_block *sb, CHAIN_T *p_dir, s32 entry); +s32 update_dir_chksum_with_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es); +bool is_dir_empty(struct super_block *sb, CHAIN_T *p_dir); +s32 mount_exfat(struct super_block *sb, pbr_t *p_pbr); + +/* amap_smart.c : creation on mount / destroy on umount */ +int amap_create(struct super_block *sb, u32 pack_ratio, u32 sect_per_au, u32 hidden_sect); +void amap_destroy(struct super_block *sb); + +/* amap_smart.c : (de)allocation functions */ +s32 amap_fat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest); +s32 amap_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse);/* Not impelmented */ +s32 amap_release_cluster(struct super_block *sb, u32 clu); /* Only update AMAP */ + +/* amap_smart.c : misc (for defrag) */ +s32 amap_mark_ignore(struct super_block *sb, u32 clu); +s32 amap_unmark_ignore(struct super_block *sb, u32 clu); +s32 amap_unmark_ignore_all(struct super_block *sb); +s32 amap_check_working(struct super_block *sb, u32 clu); +s32 amap_get_freeclus(struct super_block *sb, u32 clu); + +/* amap_smart.c : stat AU */ +u32 amap_get_au_stat(struct super_block *sb, s32 mode); + + +/* blkdev.c */ +s32 bdev_open_dev(struct super_block *sb); +s32 bdev_close_dev(struct super_block *sb); +s32 bdev_check_bdi_valid(struct super_block *sb); +s32 bdev_readahead(struct super_block *sb, u64 secno, u64 num_secs); +s32 bdev_mread(struct super_block *sb, u64 secno, struct buffer_head **bh, u64 num_secs, s32 read); +s32 bdev_mwrite(struct super_block *sb, u64 secno, struct buffer_head *bh, u64 num_secs, s32 sync); +s32 bdev_sync_all(struct super_block *sb); + +/* blkdev.c : sector read/write functions */ +s32 read_sect(struct super_block *sb, u64 sec, struct buffer_head **bh, s32 read); +s32 write_sect(struct super_block *sb, u64 sec, struct buffer_head *bh, s32 sync); +s32 read_msect(struct super_block *sb, u64 sec, struct buffer_head **bh, s64 num_secs, s32 read); +s32 write_msect(struct super_block *sb, u64 sec, struct buffer_head *bh, s64 num_secs, s32 sync); +s32 write_msect_zero(struct super_block *sb, u64 sec, u64 num_secs); + +/* misc.c */ +u8 calc_chksum_1byte(void *data, s32 len, u8 chksum); +u16 calc_chksum_2byte(void *data, s32 len, u16 chksum, s32 type); + +/* extent.c */ +s32 extent_cache_init(void); +void extent_cache_shutdown(void); +void extent_cache_init_inode(struct inode *inode); +void extent_cache_inval_inode(struct inode *inode); +s32 extent_get_clus(struct inode *inode, u32 cluster, u32 *fclus, + u32 *dclus, u32 *last_dclus, s32 allow_eof); +/*----------------------------------------------------------------------*/ +/* Wrapper Function */ +/*----------------------------------------------------------------------*/ +void set_sb_dirty(struct super_block *sb); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _SDFAT_CORE_H */ + +/* end of core.h */ diff --git a/fs/sdfat/core_exfat.c b/fs/sdfat/core_exfat.c new file mode 100644 index 0000000000000..08c45b09bb78c --- /dev/null +++ b/fs/sdfat/core_exfat.c @@ -0,0 +1,1554 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : core_exfat.c */ +/* PURPOSE : exFAT-fs core code for sdFAT */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +#include "sdfat.h" +#include "core.h" +#include +#include + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Local Variable Definitions */ +/*----------------------------------------------------------------------*/ +static u8 free_bit[] = { + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/* 0 ~ 19*/ + 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~ 39*/ + 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/* 40 ~ 59*/ + 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/* 60 ~ 79*/ + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2,/* 80 ~ 99*/ + 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3,/*100 ~ 119*/ + 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*120 ~ 139*/ + 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,/*140 ~ 159*/ + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/*160 ~ 179*/ + 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3,/*180 ~ 199*/ + 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*200 ~ 219*/ + 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/*220 ~ 239*/ + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /*240 ~ 254*/ +}; + +static u8 used_bit[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,/* 0 ~ 19*/ + 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,/* 20 ~ 39*/ + 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,/* 40 ~ 59*/ + 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,/* 60 ~ 79*/ + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,/* 80 ~ 99*/ + 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,/*100 ~ 119*/ + 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4,/*120 ~ 139*/ + 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,/*140 ~ 159*/ + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,/*160 ~ 179*/ + 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,/*180 ~ 199*/ + 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,/*200 ~ 219*/ + 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,/*220 ~ 239*/ + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 /*240 ~ 255*/ +}; + + +/*======================================================================*/ +/* Local Function Definitions */ +/*======================================================================*/ +/* + * Directory Entry Management Functions + */ +static u32 exfat_get_entry_type(DENTRY_T *p_entry) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry; + + if (ep->type == EXFAT_UNUSED) + return TYPE_UNUSED; + if (ep->type < 0x80) + return TYPE_DELETED; + if (ep->type == 0x80) + return TYPE_INVALID; + if (ep->type < 0xA0) { + if (ep->type == 0x81) + return TYPE_BITMAP; + if (ep->type == 0x82) + return TYPE_UPCASE; + if (ep->type == 0x83) + return TYPE_VOLUME; + if (ep->type == 0x85) { + if (le16_to_cpu(ep->attr) & ATTR_SUBDIR) + return TYPE_DIR; + return TYPE_FILE; + } + return TYPE_CRITICAL_PRI; + } + if (ep->type < 0xC0) { + if (ep->type == 0xA0) + return TYPE_GUID; + if (ep->type == 0xA1) + return TYPE_PADDING; + if (ep->type == 0xA2) + return TYPE_ACLTAB; + return TYPE_BENIGN_PRI; + } + if (ep->type < 0xE0) { + if (ep->type == 0xC0) + return TYPE_STREAM; + if (ep->type == 0xC1) + return TYPE_EXTEND; + if (ep->type == 0xC2) + return TYPE_ACL; + return TYPE_CRITICAL_SEC; + } + return TYPE_BENIGN_SEC; +} /* end of exfat_get_entry_type */ + +static void exfat_set_entry_type(DENTRY_T *p_entry, u32 type) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry; + + if (type == TYPE_UNUSED) { + ep->type = 0x0; + } else if (type == TYPE_DELETED) { + ep->type &= ~0x80; + } else if (type == TYPE_STREAM) { + ep->type = 0xC0; + } else if (type == TYPE_EXTEND) { + ep->type = 0xC1; + } else if (type == TYPE_BITMAP) { + ep->type = 0x81; + } else if (type == TYPE_UPCASE) { + ep->type = 0x82; + } else if (type == TYPE_VOLUME) { + ep->type = 0x83; + } else if (type == TYPE_DIR) { + ep->type = 0x85; + ep->attr = cpu_to_le16(ATTR_SUBDIR); + } else if (type == TYPE_FILE) { + ep->type = 0x85; + ep->attr = cpu_to_le16(ATTR_ARCHIVE); + } else if (type == TYPE_SYMLINK) { + ep->type = 0x85; + ep->attr = cpu_to_le16(ATTR_ARCHIVE | ATTR_SYMLINK); + } +} /* end of exfat_set_entry_type */ + +static u32 exfat_get_entry_attr(DENTRY_T *p_entry) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + return (u32)le16_to_cpu(ep->attr); +} /* end of exfat_get_entry_attr */ + +static void exfat_set_entry_attr(DENTRY_T *p_entry, u32 attr) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + ep->attr = cpu_to_le16((u16) attr); +} /* end of exfat_set_entry_attr */ + +static u8 exfat_get_entry_flag(DENTRY_T *p_entry) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + return ep->flags; +} /* end of exfat_get_entry_flag */ + +static void exfat_set_entry_flag(DENTRY_T *p_entry, u8 flags) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + ep->flags = flags; +} /* end of exfat_set_entry_flag */ + +static u32 exfat_get_entry_clu0(DENTRY_T *p_entry) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + return (u32)le32_to_cpu(ep->start_clu); +} /* end of exfat_get_entry_clu0 */ + +static void exfat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + ep->start_clu = cpu_to_le32(start_clu); +} /* end of exfat_set_entry_clu0 */ + +static u64 exfat_get_entry_size(DENTRY_T *p_entry) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + return le64_to_cpu(ep->valid_size); +} /* end of exfat_get_entry_size */ + +static void exfat_set_entry_size(DENTRY_T *p_entry, u64 size) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + ep->valid_size = cpu_to_le64(size); + ep->size = cpu_to_le64(size); +} /* end of exfat_set_entry_size */ + +static void exfat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode) +{ + u16 t = 0x00, d = 0x21; + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + switch (mode) { + case TM_CREATE: + t = le16_to_cpu(ep->create_time); + d = le16_to_cpu(ep->create_date); + break; + case TM_MODIFY: + t = le16_to_cpu(ep->modify_time); + d = le16_to_cpu(ep->modify_date); + break; + case TM_ACCESS: + t = le16_to_cpu(ep->access_time); + d = le16_to_cpu(ep->access_date); + break; + } + + tp->sec = (t & 0x001F) << 1; + tp->min = (t >> 5) & 0x003F; + tp->hour = (t >> 11); + tp->day = (d & 0x001F); + tp->mon = (d >> 5) & 0x000F; + tp->year = (d >> 9); +} /* end of exfat_get_entry_time */ + +static void exfat_set_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode) +{ + u16 t, d; + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + t = (tp->hour << 11) | (tp->min << 5) | (tp->sec >> 1); + d = (tp->year << 9) | (tp->mon << 5) | tp->day; + + switch (mode) { + case TM_CREATE: + ep->create_time = cpu_to_le16(t); + ep->create_date = cpu_to_le16(d); + break; + case TM_MODIFY: + ep->modify_time = cpu_to_le16(t); + ep->modify_date = cpu_to_le16(d); + break; + case TM_ACCESS: + ep->access_time = cpu_to_le16(t); + ep->access_date = cpu_to_le16(d); + break; + } +} /* end of exfat_set_entry_time */ + + +static void __init_file_entry(struct super_block *sb, FILE_DENTRY_T *ep, u32 type) +{ + TIMESTAMP_T tm, *tp; + + exfat_set_entry_type((DENTRY_T *) ep, type); + + tp = tm_now(SDFAT_SB(sb), &tm); + exfat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE); + exfat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY); + exfat_set_entry_time((DENTRY_T *) ep, tp, TM_ACCESS); + ep->create_time_ms = 0; + ep->modify_time_ms = 0; + ep->access_time_ms = 0; +} /* end of __init_file_entry */ + +static void __init_strm_entry(STRM_DENTRY_T *ep, u8 flags, u32 start_clu, u64 size) +{ + exfat_set_entry_type((DENTRY_T *) ep, TYPE_STREAM); + ep->flags = flags; + ep->start_clu = cpu_to_le32(start_clu); + ep->valid_size = cpu_to_le64(size); + ep->size = cpu_to_le64(size); +} /* end of __init_strm_entry */ + +static void __init_name_entry(NAME_DENTRY_T *ep, u16 *uniname) +{ + s32 i; + + exfat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND); + ep->flags = 0x0; + + for (i = 0; i < 15; i++) { + ep->unicode_0_14[i] = cpu_to_le16(*uniname); + if (*uniname == 0x0) + break; + uniname++; + } +} /* end of __init_name_entry */ + +static s32 exfat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type, u32 start_clu, u64 size) +{ + u64 sector; + u8 flags; + FILE_DENTRY_T *file_ep; + STRM_DENTRY_T *strm_ep; + + flags = (type == TYPE_FILE) ? 0x01 : 0x03; + + /* we cannot use get_dentry_set_in_dir here because file ep is not initialized yet */ + file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, §or); + if (!file_ep) + return -EIO; + + strm_ep = (STRM_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+1, §or); + if (!strm_ep) + return -EIO; + + __init_file_entry(sb, file_ep, type); + if (dcache_modify(sb, sector)) + return -EIO; + + __init_strm_entry(strm_ep, flags, start_clu, size); + if (dcache_modify(sb, sector)) + return -EIO; + + return 0; +} /* end of exfat_init_dir_entry */ + +s32 update_dir_chksum(struct super_block *sb, CHAIN_T *p_dir, s32 entry) +{ + s32 ret = -EIO; + s32 i, num_entries; + u64 sector; + u16 chksum; + FILE_DENTRY_T *file_ep; + DENTRY_T *ep; + + file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, §or); + if (!file_ep) + return -EIO; + + dcache_lock(sb, sector); + + num_entries = (s32) file_ep->num_ext + 1; + chksum = calc_chksum_2byte((void *) file_ep, DENTRY_SIZE, 0, CS_DIR_ENTRY); + + for (i = 1; i < num_entries; i++) { + ep = get_dentry_in_dir(sb, p_dir, entry+i, NULL); + if (!ep) + goto out_unlock; + + chksum = calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, CS_DEFAULT); + } + + file_ep->checksum = cpu_to_le16(chksum); + ret = dcache_modify(sb, sector); +out_unlock: + dcache_unlock(sb, sector); + return ret; + +} /* end of update_dir_chksum */ + + +static s32 exfat_init_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 num_entries, + UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname) +{ + s32 i; + u64 sector; + u16 *uniname = p_uniname->name; + FILE_DENTRY_T *file_ep; + STRM_DENTRY_T *strm_ep; + NAME_DENTRY_T *name_ep; + + file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, §or); + if (!file_ep) + return -EIO; + + file_ep->num_ext = (u8)(num_entries - 1); + dcache_modify(sb, sector); + + strm_ep = (STRM_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+1, §or); + if (!strm_ep) + return -EIO; + + strm_ep->name_len = p_uniname->name_len; + strm_ep->name_hash = cpu_to_le16(p_uniname->name_hash); + dcache_modify(sb, sector); + + for (i = 2; i < num_entries; i++) { + name_ep = (NAME_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+i, §or); + if (!name_ep) + return -EIO; + + __init_name_entry(name_ep, uniname); + dcache_modify(sb, sector); + uniname += 15; + } + + update_dir_chksum(sb, p_dir, entry); + + return 0; +} /* end of exfat_init_ext_entry */ + + +static s32 exfat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries) +{ + s32 i; + u64 sector; + DENTRY_T *ep; + + for (i = order; i < num_entries; i++) { + ep = get_dentry_in_dir(sb, p_dir, entry+i, §or); + if (!ep) + return -EIO; + + exfat_set_entry_type(ep, TYPE_DELETED); + if (dcache_modify(sb, sector)) + return -EIO; + } + + return 0; +} + +static s32 __write_partial_entries_in_entry_set(struct super_block *sb, + ENTRY_SET_CACHE_T *es, u64 sec, u32 off, u32 count) +{ + s32 num_entries; + u32 buf_off = (off - es->offset); + u32 remaining_byte_in_sector, copy_entries; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u32 clu; + u8 *buf, *esbuf = (u8 *)&(es->__buf); + + TMSG("%s entered\n", __func__); + MMSG("%s: es %p sec %llu off %u cnt %d\n", __func__, es, sec, off, count); + num_entries = count; + + while (num_entries) { + /* write per sector base */ + remaining_byte_in_sector = (1 << sb->s_blocksize_bits) - off; + copy_entries = min((s32)(remaining_byte_in_sector >> DENTRY_SIZE_BITS), num_entries); + buf = dcache_getblk(sb, sec); + if (!buf) + goto err_out; + MMSG("es->buf %p buf_off %u\n", esbuf, buf_off); + MMSG("copying %d entries from %p to sector %llu\n", copy_entries, (esbuf + buf_off), sec); + memcpy(buf + off, esbuf + buf_off, copy_entries << DENTRY_SIZE_BITS); + dcache_modify(sb, sec); + num_entries -= copy_entries; + + if (num_entries) { + // get next sector + if (IS_LAST_SECT_IN_CLUS(fsi, sec)) { + clu = SECT_TO_CLUS(fsi, sec); + if (es->alloc_flag == 0x03) + clu++; + else if (get_next_clus_safe(sb, &clu)) + goto err_out; + sec = CLUS_TO_SECT(fsi, clu); + } else { + sec++; + } + off = 0; + buf_off += copy_entries << DENTRY_SIZE_BITS; + } + } + + TMSG("%s exited successfully\n", __func__); + return 0; +err_out: + TMSG("%s failed\n", __func__); + return -EIO; +} + +/* write back all entries in entry set */ +static s32 __write_whole_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es) +{ + return __write_partial_entries_in_entry_set(sb, es, es->sector, es->offset, es->num_entries); +} + +s32 update_dir_chksum_with_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es) +{ + DENTRY_T *ep; + u16 chksum = 0; + s32 chksum_type = CS_DIR_ENTRY, i; + + ep = (DENTRY_T *)&(es->__buf); + for (i = 0; i < es->num_entries; i++) { + MMSG("%s %p\n", __func__, ep); + chksum = calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, chksum_type); + ep++; + chksum_type = CS_DEFAULT; + } + + ep = (DENTRY_T *)&(es->__buf); + ((FILE_DENTRY_T *)ep)->checksum = cpu_to_le16(chksum); + return __write_whole_entry_set(sb, es); +} + +/* returns a set of dentries for a file or dir. + * Note that this is a copy (dump) of dentries so that user should call write_entry_set() + * to apply changes made in this entry set to the real device. + * in: + * sb+p_dir+entry: indicates a file/dir + * type: specifies how many dentries should be included. + * out: + * file_ep: will point the first dentry(= file dentry) on success + * return: + * pointer of entry set on success, + * NULL on failure. + */ + +#define ES_MODE_STARTED 0 +#define ES_MODE_GET_FILE_ENTRY 1 +#define ES_MODE_GET_STRM_ENTRY 2 +#define ES_MODE_GET_NAME_ENTRY 3 +#define ES_MODE_GET_CRITICAL_SEC_ENTRY 4 +ENTRY_SET_CACHE_T *get_dentry_set_in_dir(struct super_block *sb, + CHAIN_T *p_dir, s32 entry, u32 type, DENTRY_T **file_ep) +{ + s32 ret; + u32 off, byte_offset, clu = 0; + u32 entry_type; + u64 sec; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + ENTRY_SET_CACHE_T *es = NULL; + DENTRY_T *ep, *pos; + u8 *buf; + u8 num_entries; + s32 mode = ES_MODE_STARTED; + + /* FIXME : is available in error case? */ + if (p_dir->dir == DIR_DELETED) { + EMSG("%s : access to deleted dentry\n", __func__); + BUG_ON(!fsi->prev_eio); + return NULL; + } + + TMSG("%s entered\n", __func__); + MMSG("p_dir dir %u flags %x size %d\n", p_dir->dir, p_dir->flags, p_dir->size); + MMSG("entry %d type %d\n", entry, type); + + byte_offset = entry << DENTRY_SIZE_BITS; + ret = walk_fat_chain(sb, p_dir, byte_offset, &clu); + if (ret) + return NULL; + + /* byte offset in cluster */ + byte_offset &= fsi->cluster_size - 1; + + /* byte offset in sector */ + off = byte_offset & (u32)(sb->s_blocksize - 1); + + /* sector offset in cluster */ + sec = byte_offset >> (sb->s_blocksize_bits); + sec += CLUS_TO_SECT(fsi, clu); + + buf = dcache_getblk(sb, sec); + if (!buf) + goto err_out; + + ep = (DENTRY_T *)(buf + off); + entry_type = exfat_get_entry_type(ep); + + if ((entry_type != TYPE_FILE) + && (entry_type != TYPE_DIR)) + goto err_out; + + if (type == ES_ALL_ENTRIES) + num_entries = ((FILE_DENTRY_T *)ep)->num_ext+1; + else + num_entries = type; + + MMSG("trying to malloc %lx bytes for %d entries\n", + (unsigned long)(offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries) * sizeof(DENTRY_T)), num_entries); + es = kmalloc((offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries) * sizeof(DENTRY_T)), GFP_KERNEL); + if (!es) { + EMSG("%s: failed to alloc entryset\n", __func__); + goto err_out; + } + + es->num_entries = num_entries; + es->sector = sec; + es->offset = off; + es->alloc_flag = p_dir->flags; + + pos = (DENTRY_T *) &(es->__buf); + + while (num_entries) { + // instead of copying whole sector, we will check every entry. + // this will provide minimum stablity and consistency. + entry_type = exfat_get_entry_type(ep); + + if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) + goto err_out; + + switch (mode) { + case ES_MODE_STARTED: + if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) + mode = ES_MODE_GET_FILE_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_FILE_ENTRY: + if (entry_type == TYPE_STREAM) + mode = ES_MODE_GET_STRM_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_STRM_ENTRY: + if (entry_type == TYPE_EXTEND) + mode = ES_MODE_GET_NAME_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_NAME_ENTRY: + if (entry_type == TYPE_EXTEND) + break; + else if (entry_type == TYPE_STREAM) + goto err_out; + else if (entry_type & TYPE_CRITICAL_SEC) + mode = ES_MODE_GET_CRITICAL_SEC_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_CRITICAL_SEC_ENTRY: + if ((entry_type == TYPE_EXTEND) || (entry_type == TYPE_STREAM)) + goto err_out; + else if ((entry_type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC) + goto err_out; + break; + } + + /* copy dentry */ + memcpy(pos, ep, sizeof(DENTRY_T)); + + if (--num_entries == 0) + break; + + if (((off + DENTRY_SIZE) & (u32)(sb->s_blocksize - 1)) < + (off & (u32)(sb->s_blocksize - 1))) { + // get the next sector + if (IS_LAST_SECT_IN_CLUS(fsi, sec)) { + if (es->alloc_flag == 0x03) + clu++; + else if (get_next_clus_safe(sb, &clu)) + goto err_out; + sec = CLUS_TO_SECT(fsi, clu); + } else { + sec++; + } + buf = dcache_getblk(sb, sec); + if (!buf) + goto err_out; + off = 0; + ep = (DENTRY_T *)(buf); + } else { + ep++; + off += DENTRY_SIZE; + } + pos++; + } + + if (file_ep) + *file_ep = (DENTRY_T *)&(es->__buf); + + MMSG("es sec %llu offset %u flags %d, num_entries %u buf ptr %p\n", + es->sector, es->offset, es->alloc_flag, es->num_entries, &(es->__buf)); + TMSG("%s exited %p\n", __func__, es); + return es; +err_out: + TMSG("%s exited (return NULL) (es %p)\n", __func__, es); + + /* kfree(NULL) is safe */ + kfree(es); + es = NULL; + return NULL; +} + +void release_dentry_set(ENTRY_SET_CACHE_T *es) +{ + TMSG("%s %p\n", __func__, es); + + /* kfree(NULL) is safe */ + kfree(es); + es = NULL; +} + +static s32 __extract_uni_name_from_name_entry(NAME_DENTRY_T *ep, u16 *uniname, s32 order) +{ + s32 i, len = 0; + + for (i = 0; i < 15; i++) { + /* FIXME : unaligned? */ + *uniname = le16_to_cpu(ep->unicode_0_14[i]); + if (*uniname == 0x0) + return len; + uniname++; + len++; + } + + *uniname = 0x0; + return len; + +} /* end of __extract_uni_name_from_name_entry */ + +#define DIRENT_STEP_FILE (0) +#define DIRENT_STEP_STRM (1) +#define DIRENT_STEP_NAME (2) +#define DIRENT_STEP_SECD (3) + +/* return values of exfat_find_dir_entry() + * >= 0 : return dir entiry position with the name in dir + * -EEXIST : (root dir, ".") it is the root dir itself + * -ENOENT : entry with the name does not exist + * -EIO : I/O error + */ +static s32 exfat_find_dir_entry(struct super_block *sb, FILE_ID_T *fid, + CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *unused, u32 type) +{ + s32 i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; + s32 order, step, name_len; + s32 dentries_per_clu, num_empty = 0; + u32 entry_type; + u16 entry_uniname[16], *uniname = NULL, unichar; + CHAIN_T clu; + DENTRY_T *ep; + HINT_T *hint_stat = &fid->hint_stat; + HINT_FEMP_T candi_empty; + FILE_DENTRY_T *file_ep; + STRM_DENTRY_T *strm_ep; + NAME_DENTRY_T *name_ep; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* + * REMARK: + * DOT and DOTDOT are handled by VFS layer + */ + + if (IS_CLUS_FREE(p_dir->dir)) + return -EIO; + + dentries_per_clu = fsi->dentries_per_clu; + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + if (hint_stat->eidx) { + clu.dir = hint_stat->clu; + dentry = hint_stat->eidx; + end_eidx = dentry; + } + + candi_empty.eidx = -1; +rewind: + order = 0; + step = DIRENT_STEP_FILE; + while (!IS_CLUS_EOF(clu.dir)) { + i = dentry & (dentries_per_clu - 1); + for (; i < dentries_per_clu; i++, dentry++) { + if (rewind && (dentry == end_eidx)) + goto not_found; + + ep = get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + entry_type = exfat_get_entry_type(ep); + + if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) { + step = DIRENT_STEP_FILE; + + num_empty++; + if (candi_empty.eidx == -1) { + if (num_empty == 1) { + candi_empty.cur.dir = clu.dir; + candi_empty.cur.size = clu.size; + candi_empty.cur.flags = clu.flags; + } + + if (num_empty >= num_entries) { + candi_empty.eidx = dentry - (num_empty - 1); + ASSERT(0 <= candi_empty.eidx); + candi_empty.count = num_empty; + + if ((fid->hint_femp.eidx == -1) || + (candi_empty.eidx <= fid->hint_femp.eidx)) { + memcpy(&fid->hint_femp, + &candi_empty, + sizeof(HINT_FEMP_T)); + } + } + } + + if (entry_type == TYPE_UNUSED) + goto not_found; + continue; + } + + num_empty = 0; + candi_empty.eidx = -1; + + if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) { + step = DIRENT_STEP_FILE; + if ((type == TYPE_ALL) || (type == entry_type)) { + file_ep = (FILE_DENTRY_T *) ep; + num_ext = file_ep->num_ext; + step = DIRENT_STEP_STRM; + } + continue; + } + + if (entry_type == TYPE_STREAM) { + if (step != DIRENT_STEP_STRM) { + step = DIRENT_STEP_FILE; + continue; + } + step = DIRENT_STEP_FILE; + strm_ep = (STRM_DENTRY_T *) ep; + if ((p_uniname->name_hash == le16_to_cpu(strm_ep->name_hash)) && + (p_uniname->name_len == strm_ep->name_len)) { + step = DIRENT_STEP_NAME; + order = 1; + name_len = 0; + } + continue; + } + + if (entry_type == TYPE_EXTEND) { + if (step != DIRENT_STEP_NAME) { + step = DIRENT_STEP_FILE; + continue; + } + name_ep = (NAME_DENTRY_T *) ep; + + if ((++order) == 2) + uniname = p_uniname->name; + else + uniname += 15; + + len = __extract_uni_name_from_name_entry(name_ep, entry_uniname, order); + name_len += len; + + unichar = *(uniname+len); + *(uniname+len) = 0x0; + + if (nls_cmp_uniname(sb, uniname, entry_uniname)) { + step = DIRENT_STEP_FILE; + } else if (name_len == p_uniname->name_len) { + if (order == num_ext) { + //fid->hint_femp.eidx = -1; + goto found; + } + step = DIRENT_STEP_SECD; + } + + *(uniname+len) = unichar; + continue; + } + + if (entry_type & (TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) { + if (step == DIRENT_STEP_SECD) { + if (++order == num_ext) + goto found; + continue; + } + } + step = DIRENT_STEP_FILE; + } + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &clu.dir)) + return -EIO; + } + } + +not_found: + /* we started at not 0 index,so we should try to find target + * from 0 index to the index we started at. + */ + if (!rewind && end_eidx) { + rewind = 1; + dentry = 0; + clu.dir = p_dir->dir; + /* reset empty hint */ + num_empty = 0; + candi_empty.eidx = -1; + goto rewind; + } + + /* initialized hint_stat */ + hint_stat->clu = p_dir->dir; + hint_stat->eidx = 0; + return -ENOENT; + +found: + /* next dentry we'll find is out of this cluster */ + if (!((dentry + 1) & (dentries_per_clu-1))) { + int ret = 0; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + ret = get_next_clus_safe(sb, &clu.dir); + } + + if (ret || IS_CLUS_EOF(clu.dir)) { + /* just initialized hint_stat */ + hint_stat->clu = p_dir->dir; + hint_stat->eidx = 0; + return (dentry - num_ext); + } + } + + hint_stat->clu = clu.dir; + hint_stat->eidx = dentry + 1; + return (dentry - num_ext); +} /* end of exfat_find_dir_entry */ + +/* returns -EIO on error */ +static s32 exfat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry) +{ + s32 i, count = 0; + u32 type; + FILE_DENTRY_T *file_ep = (FILE_DENTRY_T *) p_entry; + DENTRY_T *ext_ep; + + for (i = 0, entry++; i < file_ep->num_ext; i++, entry++) { + ext_ep = get_dentry_in_dir(sb, p_dir, entry, NULL); + if (!ext_ep) + return -EIO; + + type = exfat_get_entry_type(ext_ep); + if ((type == TYPE_EXTEND) || (type == TYPE_STREAM)) + count++; + else + return count; + } + + return count; +} /* end of exfat_count_ext_entries */ + + +/* + * Name Conversion Functions + */ +static void exfat_get_uniname_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname) +{ + s32 i; + DENTRY_T *ep; + ENTRY_SET_CACHE_T *es; + + es = get_dentry_set_in_dir(sb, p_dir, entry, ES_ALL_ENTRIES, &ep); + if (!es) + return; + + if (es->num_entries < 3) + goto out; + + ep += 2; + + /* + * First entry : file entry + * Second entry : stream-extension entry + * Third entry : first file-name entry + * So, the index of first file-name dentry should start from 2. + */ + for (i = 2; i < es->num_entries; i++, ep++) { + /* end of name entry */ + if (exfat_get_entry_type(ep) != TYPE_EXTEND) + goto out; + + __extract_uni_name_from_name_entry((NAME_DENTRY_T *)ep, uniname, i); + uniname += 15; + } + +out: + release_dentry_set(es); +} /* end of exfat_get_uniname_from_ext_entry */ + +static s32 exfat_calc_num_entries(UNI_NAME_T *p_uniname) +{ + s32 len; + + len = p_uniname->name_len; + if (len == 0) + return 0; + + /* 1 file entry + 1 stream entry + name entries */ + return((len-1) / 15 + 3); + +} /* end of exfat_calc_num_entries */ + +static s32 exfat_check_max_dentries(FILE_ID_T *fid) +{ + if ((fid->size >> DENTRY_SIZE_BITS) >= MAX_EXFAT_DENTRIES) { + /* exFAT spec allows a dir to grow upto 8388608(256MB) dentries */ + return -ENOSPC; + } + return 0; +} /* end of check_max_dentries */ + +/* + * Allocation Bitmap Management Functions + */ +s32 load_alloc_bmp(struct super_block *sb) +{ + s32 ret; + u32 i, j, map_size, type, need_map_size; + u64 sector; + CHAIN_T clu; + BMAP_DENTRY_T *ep; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + clu.dir = fsi->root_dir; + clu.flags = 0x01; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < fsi->dentries_per_clu; i++) { + ep = (BMAP_DENTRY_T *) get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = exfat_get_entry_type((DENTRY_T *) ep); + + if (type == TYPE_UNUSED) + break; + if (type != TYPE_BITMAP) + continue; + + if (ep->flags == 0x0) { + fsi->map_clu = le32_to_cpu(ep->start_clu); + map_size = (u32) le64_to_cpu(ep->size); + + need_map_size = (((fsi->num_clusters - CLUS_BASE) - 1) >> 3) + 1; + if (need_map_size != map_size) { + sdfat_log_msg(sb, KERN_ERR, + "bogus allocation bitmap size(need : %u, cur : %u)", + need_map_size, map_size); + /* Only allowed when bogus allocation bitmap size is large */ + if (need_map_size > map_size) + return -EIO; + } + fsi->map_sectors = ((need_map_size - 1) >> (sb->s_blocksize_bits)) + 1; + fsi->vol_amap = + kmalloc((sizeof(struct buffer_head *) * fsi->map_sectors), GFP_KERNEL); + if (!fsi->vol_amap) + return -ENOMEM; + + sector = CLUS_TO_SECT(fsi, fsi->map_clu); + + for (j = 0; j < fsi->map_sectors; j++) { + fsi->vol_amap[j] = NULL; + ret = read_sect(sb, sector+j, &(fsi->vol_amap[j]), 1); + if (ret) { + /* release all buffers and free vol_amap */ + i = 0; + while (i < j) + brelse(fsi->vol_amap[i++]); + + /* kfree(NULL) is safe */ + kfree(fsi->vol_amap); + fsi->vol_amap = NULL; + return ret; + } + } + + fsi->pbr_bh = NULL; + return 0; + } + } + + if (get_next_clus_safe(sb, &clu.dir)) + return -EIO; + } + + return -EINVAL; +} /* end of load_alloc_bmp */ + +void free_alloc_bmp(struct super_block *sb) +{ + s32 i; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + brelse(fsi->pbr_bh); + + for (i = 0; i < fsi->map_sectors; i++) + __brelse(fsi->vol_amap[i]); + + /* kfree(NULL) is safe */ + kfree(fsi->vol_amap); + fsi->vol_amap = NULL; +} + +/* WARN : + * If the value of "clu" is 0, it means cluster 2 which is + * the first cluster of cluster heap. + */ +static s32 set_alloc_bitmap(struct super_block *sb, u32 clu) +{ + s32 i, b; + u64 sector; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + i = clu >> (sb->s_blocksize_bits + 3); + b = clu & (u32)((sb->s_blocksize << 3) - 1); + + sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i; + bitmap_set((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1); + + return write_sect(sb, sector, fsi->vol_amap[i], 0); +} /* end of set_alloc_bitmap */ + +/* WARN : + * If the value of "clu" is 0, it means cluster 2 which is + * the first cluster of cluster heap. + */ +static s32 clr_alloc_bitmap(struct super_block *sb, u32 clu) +{ + s32 ret; + s32 i, b; + u64 sector; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct sdfat_mount_options *opts = &sbi->options; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + i = clu >> (sb->s_blocksize_bits + 3); + b = clu & (u32)((sb->s_blocksize << 3) - 1); + + sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i; + + bitmap_clear((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1); + + ret = write_sect(sb, sector, fsi->vol_amap[i], 0); + + if (opts->discard) { + s32 ret_discard; + + TMSG("discard cluster(%08x)\n", clu+2); + ret_discard = sb_issue_discard(sb, CLUS_TO_SECT(fsi, clu+2), + (1 << fsi->sect_per_clus_bits), GFP_NOFS, 0); + + if (ret_discard == -EOPNOTSUPP) { + sdfat_msg(sb, KERN_ERR, + "discard not supported by device, disabling"); + opts->discard = 0; + } + } + + return ret; +} /* end of clr_alloc_bitmap */ + +/* WARN : + * If the value of "clu" is 0, it means cluster 2 which is + * the first cluster of cluster heap. + */ +static u32 test_alloc_bitmap(struct super_block *sb, u32 clu) +{ + u32 i, map_i, map_b; + u32 clu_base, clu_free; + u8 k, clu_mask; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + clu_base = (clu & ~(0x7)) + 2; + clu_mask = (1 << (clu - clu_base + 2)) - 1; + + map_i = clu >> (sb->s_blocksize_bits + 3); + map_b = (clu >> 3) & (u32)(sb->s_blocksize - 1); + + for (i = 2; i < fsi->num_clusters; i += 8) { + k = *(((u8 *) fsi->vol_amap[map_i]->b_data) + map_b); + if (clu_mask > 0) { + k |= clu_mask; + clu_mask = 0; + } + if (k < 0xFF) { + clu_free = clu_base + free_bit[k]; + if (clu_free < fsi->num_clusters) + return clu_free; + } + clu_base += 8; + + if (((++map_b) >= (u32)sb->s_blocksize) || + (clu_base >= fsi->num_clusters)) { + if ((++map_i) >= fsi->map_sectors) { + clu_base = 2; + map_i = 0; + } + map_b = 0; + } + } + + return CLUS_EOF; +} /* end of test_alloc_bitmap */ + +void sync_alloc_bmp(struct super_block *sb) +{ + s32 i; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (fsi->vol_amap == NULL) + return; + + for (i = 0; i < fsi->map_sectors; i++) + sync_dirty_buffer(fsi->vol_amap[i]); +} + +static s32 exfat_chain_cont_cluster(struct super_block *sb, u32 chain, u32 len) +{ + if (!len) + return 0; + + while (len > 1) { + if (fat_ent_set(sb, chain, chain+1)) + return -EIO; + chain++; + len--; + } + + if (fat_ent_set(sb, chain, CLUS_EOF)) + return -EIO; + return 0; +} + +s32 chain_cont_cluster(struct super_block *sb, u32 chain, u32 len) +{ + return exfat_chain_cont_cluster(sb, chain, len); +} + + +static s32 exfat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse) +{ + s32 ret = -EIO; + u32 num_clusters = 0; + u32 clu; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 i; + u64 sector; + + /* invalid cluster number */ + if (IS_CLUS_FREE(p_chain->dir) || IS_CLUS_EOF(p_chain->dir)) + return 0; + + /* no cluster to truncate */ + if (p_chain->size == 0) { + DMSG("%s: cluster(%u) truncation is not required.", + __func__, p_chain->dir); + return 0; + } + + /* check cluster validation */ + if ((p_chain->dir < 2) && (p_chain->dir >= fsi->num_clusters)) { + EMSG("%s: invalid start cluster (%u)\n", __func__, p_chain->dir); + sdfat_debug_bug_on(1); + return -EIO; + } + + set_sb_dirty(sb); + clu = p_chain->dir; + + if (p_chain->flags == 0x03) { + do { + if (do_relse) { + sector = CLUS_TO_SECT(fsi, clu); + for (i = 0; i < fsi->sect_per_clus; i++) { + if (dcache_release(sb, sector+i) == -EIO) + goto out; + } + } + + if (clr_alloc_bitmap(sb, clu-2)) + goto out; + clu++; + + num_clusters++; + } while (num_clusters < p_chain->size); + } else { + do { + if (do_relse) { + sector = CLUS_TO_SECT(fsi, clu); + for (i = 0; i < fsi->sect_per_clus; i++) { + if (dcache_release(sb, sector+i) == -EIO) + goto out; + } + } + + if (clr_alloc_bitmap(sb, (clu - CLUS_BASE))) + goto out; + + if (get_next_clus_safe(sb, &clu)) + goto out; + + num_clusters++; + } while (!IS_CLUS_EOF(clu)); + } + + /* success */ + ret = 0; +out: + + fsi->used_clusters -= num_clusters; + return ret; +} /* end of exfat_free_cluster */ + +static s32 exfat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest) +{ + s32 ret = -ENOSPC; + u32 num_clusters = 0, total_cnt; + u32 hint_clu, new_clu, last_clu = CLUS_EOF; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + total_cnt = fsi->num_clusters - CLUS_BASE; + + if (unlikely(total_cnt < fsi->used_clusters)) { + sdfat_fs_error_ratelimit(sb, + "%s: invalid used clusters(t:%u,u:%u)\n", + __func__, total_cnt, fsi->used_clusters); + return -EIO; + } + + if (num_alloc > total_cnt - fsi->used_clusters) + return -ENOSPC; + + hint_clu = p_chain->dir; + /* find new cluster */ + if (IS_CLUS_EOF(hint_clu)) { + if (fsi->clu_srch_ptr < CLUS_BASE) { + EMSG("%s: fsi->clu_srch_ptr is invalid (%u)\n", + __func__, fsi->clu_srch_ptr); + ASSERT(0); + fsi->clu_srch_ptr = CLUS_BASE; + } + + hint_clu = test_alloc_bitmap(sb, fsi->clu_srch_ptr - CLUS_BASE); + if (IS_CLUS_EOF(hint_clu)) + return -ENOSPC; + } + + /* check cluster validation */ + if ((hint_clu < CLUS_BASE) && (hint_clu >= fsi->num_clusters)) { + EMSG("%s: hint_cluster is invalid (%u)\n", __func__, hint_clu); + ASSERT(0); + hint_clu = CLUS_BASE; + if (p_chain->flags == 0x03) { + if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) + return -EIO; + p_chain->flags = 0x01; + } + } + + set_sb_dirty(sb); + + p_chain->dir = CLUS_EOF; + + while ((new_clu = test_alloc_bitmap(sb, hint_clu - CLUS_BASE)) != CLUS_EOF) { + if ((new_clu != hint_clu) && (p_chain->flags == 0x03)) { + if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) { + ret = -EIO; + goto error; + } + p_chain->flags = 0x01; + } + + /* update allocation bitmap */ + if (set_alloc_bitmap(sb, new_clu - CLUS_BASE)) { + ret = -EIO; + goto error; + } + + num_clusters++; + + /* update FAT table */ + if (p_chain->flags == 0x01) { + if (fat_ent_set(sb, new_clu, CLUS_EOF)) { + ret = -EIO; + goto error; + } + } + + if (IS_CLUS_EOF(p_chain->dir)) { + p_chain->dir = new_clu; + } else if (p_chain->flags == 0x01) { + if (fat_ent_set(sb, last_clu, new_clu)) { + ret = -EIO; + goto error; + } + } + last_clu = new_clu; + + if ((--num_alloc) == 0) { + fsi->clu_srch_ptr = hint_clu; + fsi->used_clusters += num_clusters; + + p_chain->size += num_clusters; + return 0; + } + + hint_clu = new_clu + 1; + if (hint_clu >= fsi->num_clusters) { + hint_clu = CLUS_BASE; + + if (p_chain->flags == 0x03) { + if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) { + ret = -EIO; + goto error; + } + p_chain->flags = 0x01; + } + } + } +error: + if (num_clusters) + exfat_free_cluster(sb, p_chain, 0); + return ret; +} /* end of exfat_alloc_cluster */ + +static s32 exfat_count_used_clusters(struct super_block *sb, u32 *ret_count) +{ + u32 count = 0; + u32 i, map_i, map_b; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u32 total_clus = fsi->num_clusters - 2; + + map_i = map_b = 0; + + for (i = 0; i < total_clus; i += 8) { + u8 k = *(((u8 *) fsi->vol_amap[map_i]->b_data) + map_b); + + count += used_bit[k]; + if ((++map_b) >= (u32)sb->s_blocksize) { + map_i++; + map_b = 0; + } + } + + /* FIXME : abnormal bitmap count should be handled as more smart */ + if (total_clus < count) + count = total_clus; + + *ret_count = count; + return 0; +} /* end of exfat_count_used_clusters */ + + +/* + * File Operation Functions + */ +static FS_FUNC_T exfat_fs_func = { + .alloc_cluster = exfat_alloc_cluster, + .free_cluster = exfat_free_cluster, + .count_used_clusters = exfat_count_used_clusters, + + .init_dir_entry = exfat_init_dir_entry, + .init_ext_entry = exfat_init_ext_entry, + .find_dir_entry = exfat_find_dir_entry, + .delete_dir_entry = exfat_delete_dir_entry, + .get_uniname_from_ext_entry = exfat_get_uniname_from_ext_entry, + .count_ext_entries = exfat_count_ext_entries, + .calc_num_entries = exfat_calc_num_entries, + .check_max_dentries = exfat_check_max_dentries, + + .get_entry_type = exfat_get_entry_type, + .set_entry_type = exfat_set_entry_type, + .get_entry_attr = exfat_get_entry_attr, + .set_entry_attr = exfat_set_entry_attr, + .get_entry_flag = exfat_get_entry_flag, + .set_entry_flag = exfat_set_entry_flag, + .get_entry_clu0 = exfat_get_entry_clu0, + .set_entry_clu0 = exfat_set_entry_clu0, + .get_entry_size = exfat_get_entry_size, + .set_entry_size = exfat_set_entry_size, + .get_entry_time = exfat_get_entry_time, + .set_entry_time = exfat_set_entry_time, +}; + +s32 mount_exfat(struct super_block *sb, pbr_t *p_pbr) +{ + pbr64_t *p_bpb = (pbr64_t *)p_pbr; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (!p_bpb->bsx.num_fats) { + sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure"); + return -EINVAL; + } + + fsi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits; + fsi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits; + fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits; + fsi->cluster_size = 1 << fsi->cluster_size_bits; + + fsi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length); + + fsi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset); + if (p_bpb->bsx.num_fats == 1) + fsi->FAT2_start_sector = fsi->FAT1_start_sector; + else + fsi->FAT2_start_sector = fsi->FAT1_start_sector + fsi->num_FAT_sectors; + + fsi->root_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset); + fsi->data_start_sector = fsi->root_start_sector; + + fsi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length); + fsi->num_clusters = le32_to_cpu(p_bpb->bsx.clu_count) + 2; + /* because the cluster index starts with 2 */ + + fsi->vol_type = EXFAT; + fsi->vol_id = le32_to_cpu(p_bpb->bsx.vol_serial); + + fsi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster); + fsi->dentries_in_root = 0; + fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS); + + fsi->vol_flag = (u32) le16_to_cpu(p_bpb->bsx.vol_flags); + fsi->clu_srch_ptr = CLUS_BASE; + fsi->used_clusters = (u32) ~0; + + fsi->fs_func = &exfat_fs_func; + fat_ent_ops_init(sb); + + if (p_bpb->bsx.vol_flags & VOL_DIRTY) { + fsi->vol_flag |= VOL_DIRTY; + sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly " + "unmounted. Some data may be corrupt. " + "Please run fsck."); + } + + return 0; +} /* end of mount_exfat */ + +/* end of core_exfat.c */ diff --git a/fs/sdfat/core_fat.c b/fs/sdfat/core_fat.c new file mode 100644 index 0000000000000..493e5cb7e71a4 --- /dev/null +++ b/fs/sdfat/core_fat.c @@ -0,0 +1,1463 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : core_fat.c */ +/* PURPOSE : FAT-fs core code for sdFAT */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +#include "sdfat.h" +#include "core.h" +#include +#include + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ +#define MAX_LFN_ORDER (20) + +/* + * MAX_EST_AU_SECT should be changed according to 32/64bits. + * On 32bit, 4KB page supports 512 clusters per AU. + * But, on 64bit, 4KB page can handle a half of total list_head of 32bit's. + * Bcause the size of list_head structure on 64bit increases twofold over 32bit. + */ +#if (BITS_PER_LONG == 64) +//#define MAX_EST_AU_SECT (16384) /* upto 8MB */ +#define MAX_EST_AU_SECT (32768) /* upto 16MB, used more page for list_head */ +#else +#define MAX_EST_AU_SECT (32768) /* upto 16MB */ +#endif + +/*======================================================================*/ +/* Local Function Declarations */ +/*======================================================================*/ +static s32 __extract_uni_name_from_ext_entry(EXT_DENTRY_T *, u16 *, s32); + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Local Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*======================================================================*/ +/* Local Function Definitions */ +/*======================================================================*/ +static u32 __calc_default_au_size(struct super_block *sb) +{ + struct block_device *bdev = sb->s_bdev; + struct gendisk *disk; + struct request_queue *queue; + struct queue_limits *limit; + unsigned int est_au_sect = MAX_EST_AU_SECT; + unsigned int est_au_size = 0; + unsigned int queue_au_size = 0; + sector_t total_sect = 0; + + /* we assumed that sector size is 512 bytes */ + + disk = bdev->bd_disk; + if (!disk) + goto out; + + queue = disk->queue; + if (!queue) + goto out; + + limit = &queue->limits; + queue_au_size = limit->discard_granularity; + + /* estimate function(x) = + * (total_sect / 2) * 512 / 1024 + * => (total_sect >> 1) >> 1) + * => (total_sect >> 2) + * => estimated bytes size + * + * ex1) <= 8GB -> 4MB + * ex2) 16GB -> 8MB + * ex3) >= 32GB -> 16MB + */ + total_sect = disk->part0.nr_sects; + est_au_size = total_sect >> 2; + + /* au_size assumed that bytes per sector is 512 */ + est_au_sect = est_au_size >> 9; + + MMSG("DBG1: total_sect(%llu) est_au_size(%u) est_au_sect(%u)\n", + (u64)total_sect, est_au_size, est_au_sect); + + if (est_au_sect <= 8192) { + /* 4MB */ + est_au_sect = 8192; + } else if (est_au_sect <= 16384) { + /* 8MB */ + est_au_sect = 16384; + } else { + /* 8MB or 16MB */ + est_au_sect = MAX_EST_AU_SECT; + } + + MMSG("DBG2: total_sect(%llu) est_au_size(%u) est_au_sect(%u)\n", + (u64)total_sect, est_au_size, est_au_sect); + + if (est_au_size < queue_au_size && + queue_au_size <= (MAX_EST_AU_SECT << 9)) { + DMSG("use queue_au_size(%u) instead of est_au_size(%u)\n", + queue_au_size, est_au_size); + est_au_sect = queue_au_size >> 9; + } + +out: + if (sb->s_blocksize != 512) { + ASSERT(sb->s_blocksize_bits > 9); + sdfat_log_msg(sb, KERN_INFO, + "adjustment est_au_size by logical block size(%lu)", + sb->s_blocksize); + est_au_sect >>= (sb->s_blocksize_bits - 9); + } + + sdfat_log_msg(sb, KERN_INFO, "set default AU sectors : %u " + "(queue_au_size : %u KB, disk_size : %llu MB)", + est_au_sect, queue_au_size >> 10, (u64)(total_sect >> 11)); + return est_au_sect; +} + + +/* + * Cluster Management Functions + */ +static s32 fat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse) +{ + s32 ret = -EIO; + s32 num_clusters = 0; + u32 clu, prev; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 i; + u64 sector; + + /* invalid cluster number */ + if (IS_CLUS_FREE(p_chain->dir) || IS_CLUS_EOF(p_chain->dir)) + return 0; + + /* no cluster to truncate */ + if (!p_chain->size) { + DMSG("%s: cluster(%u) truncation is not required.", + __func__, p_chain->dir); + return 0; + } + + /* check cluster validation */ + if ((p_chain->dir < 2) && (p_chain->dir >= fsi->num_clusters)) { + EMSG("%s: invalid start cluster (%u)\n", __func__, p_chain->dir); + sdfat_debug_bug_on(1); + return -EIO; + } + + + set_sb_dirty(sb); + clu = p_chain->dir; + + do { + if (do_relse) { + sector = CLUS_TO_SECT(fsi, clu); + for (i = 0; i < fsi->sect_per_clus; i++) { + if (dcache_release(sb, sector+i) == -EIO) + goto out; + } + } + + prev = clu; + if (get_next_clus_safe(sb, &clu)) { + /* print more helpful log */ + if (IS_CLUS_BAD(clu)) { + sdfat_log_msg(sb, KERN_ERR, "%s : " + "deleting bad cluster (clu[%u]->BAD)", + __func__, prev); + } else if (IS_CLUS_FREE(clu)) { + sdfat_log_msg(sb, KERN_ERR, "%s : " + "deleting free cluster (clu[%u]->FREE)", + __func__, prev); + } + goto out; + } + + /* Free FAT chain */ + if (fat_ent_set(sb, prev, CLUS_FREE)) + goto out; + + /* Update AMAP if needed */ + if (fsi->amap) { + if (amap_release_cluster(sb, prev)) + return -EIO; + } + + num_clusters++; + + } while (!IS_CLUS_EOF(clu)); + + /* success */ + ret = 0; +out: + fsi->used_clusters -= num_clusters; + return ret; +} /* end of fat_free_cluster */ + +static s32 fat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest) +{ + s32 ret = -ENOSPC; + u32 i, num_clusters = 0, total_cnt; + u32 new_clu, last_clu = CLUS_EOF, read_clu; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + total_cnt = fsi->num_clusters - CLUS_BASE; + + if (unlikely(total_cnt < fsi->used_clusters)) { + sdfat_fs_error_ratelimit(sb, + "%s : invalid used clusters(t:%u,u:%u)\n", + __func__, total_cnt, fsi->used_clusters); + return -EIO; + } + + if (num_alloc > total_cnt - fsi->used_clusters) + return -ENOSPC; + + new_clu = p_chain->dir; + if (IS_CLUS_EOF(new_clu)) + new_clu = fsi->clu_srch_ptr; + else if (new_clu >= fsi->num_clusters) + new_clu = CLUS_BASE; + + set_sb_dirty(sb); + + p_chain->dir = CLUS_EOF; + + for (i = CLUS_BASE; i < fsi->num_clusters; i++) { + if (fat_ent_get(sb, new_clu, &read_clu)) { + ret = -EIO; + goto error; + } + + if (IS_CLUS_FREE(read_clu)) { + if (fat_ent_set(sb, new_clu, CLUS_EOF)) { + ret = -EIO; + goto error; + } + num_clusters++; + + if (IS_CLUS_EOF(p_chain->dir)) { + p_chain->dir = new_clu; + } else { + if (fat_ent_set(sb, last_clu, new_clu)) { + ret = -EIO; + goto error; + } + } + + last_clu = new_clu; + + if ((--num_alloc) == 0) { + fsi->clu_srch_ptr = new_clu; + fsi->used_clusters += num_clusters; + + return 0; + } + } + if ((++new_clu) >= fsi->num_clusters) + new_clu = CLUS_BASE; + } +error: + if (num_clusters) + fat_free_cluster(sb, p_chain, 0); + return ret; +} /* end of fat_alloc_cluster */ + +static s32 fat_count_used_clusters(struct super_block *sb, u32 *ret_count) +{ + s32 i; + u32 clu, count = 0; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + for (i = CLUS_BASE; i < fsi->num_clusters; i++) { + if (fat_ent_get(sb, i, &clu)) + return -EIO; + + if (!IS_CLUS_FREE(clu)) + count++; + } + + *ret_count = count; + return 0; +} /* end of fat_count_used_clusters */ + + +/* + * Directory Entry Management Functions + */ +static u32 fat_get_entry_type(DENTRY_T *p_entry) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + + /* first byte of 32bytes dummy */ + if (*(ep->name) == MSDOS_UNUSED) + return TYPE_UNUSED; + + /* 0xE5 of Kanji Japanese is replaced to 0x05 */ + else if (*(ep->name) == MSDOS_DELETED) + return TYPE_DELETED; + + /* 11th byte of 32bytes dummy */ + else if ((ep->attr & ATTR_EXTEND_MASK) == ATTR_EXTEND) + return TYPE_EXTEND; + + else if (!(ep->attr & (ATTR_SUBDIR | ATTR_VOLUME))) + return TYPE_FILE; + + else if ((ep->attr & (ATTR_SUBDIR | ATTR_VOLUME)) == ATTR_SUBDIR) + return TYPE_DIR; + + else if ((ep->attr & (ATTR_SUBDIR | ATTR_VOLUME)) == ATTR_VOLUME) + return TYPE_VOLUME; + + return TYPE_INVALID; +} /* end of fat_get_entry_type */ + +static void fat_set_entry_type(DENTRY_T *p_entry, u32 type) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + + if (type == TYPE_UNUSED) + *(ep->name) = MSDOS_UNUSED; /* 0x0 */ + + else if (type == TYPE_DELETED) + *(ep->name) = MSDOS_DELETED; /* 0xE5 */ + + else if (type == TYPE_EXTEND) + ep->attr = ATTR_EXTEND; + + else if (type == TYPE_DIR) + ep->attr = ATTR_SUBDIR; + + else if (type == TYPE_FILE) + ep->attr = ATTR_ARCHIVE; + + else if (type == TYPE_SYMLINK) + ep->attr = ATTR_ARCHIVE | ATTR_SYMLINK; +} /* end of fat_set_entry_type */ + +static u32 fat_get_entry_attr(DENTRY_T *p_entry) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + + return (u32)ep->attr; +} /* end of fat_get_entry_attr */ + +static void fat_set_entry_attr(DENTRY_T *p_entry, u32 attr) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + + ep->attr = (u8)attr; +} /* end of fat_set_entry_attr */ + +static u8 fat_get_entry_flag(DENTRY_T *p_entry) +{ + return 0x01; +} /* end of fat_get_entry_flag */ + +static void fat_set_entry_flag(DENTRY_T *p_entry, u8 flags) +{ +} /* end of fat_set_entry_flag */ + +static u32 fat_get_entry_clu0(DENTRY_T *p_entry) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + /* FIXME : is ok? */ + return(((u32)(le16_to_cpu(ep->start_clu_hi)) << 16) | le16_to_cpu(ep->start_clu_lo)); +} /* end of fat_get_entry_clu0 */ + +static void fat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + + ep->start_clu_lo = cpu_to_le16(CLUSTER_16(start_clu)); + ep->start_clu_hi = cpu_to_le16(CLUSTER_16(start_clu >> 16)); +} /* end of fat_set_entry_clu0 */ + +static u64 fat_get_entry_size(DENTRY_T *p_entry) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + + return (u64)le32_to_cpu(ep->size); +} /* end of fat_get_entry_size */ + +static void fat_set_entry_size(DENTRY_T *p_entry, u64 size) +{ + DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry; + + ep->size = cpu_to_le32((u32)size); +} /* end of fat_set_entry_size */ + +static void fat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode) +{ + u16 t = 0x00, d = 0x21; + DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry; + + switch (mode) { + case TM_CREATE: + t = le16_to_cpu(ep->create_time); + d = le16_to_cpu(ep->create_date); + break; + case TM_MODIFY: + t = le16_to_cpu(ep->modify_time); + d = le16_to_cpu(ep->modify_date); + break; + } + + tp->sec = (t & 0x001F) << 1; + tp->min = (t >> 5) & 0x003F; + tp->hour = (t >> 11); + tp->day = (d & 0x001F); + tp->mon = (d >> 5) & 0x000F; + tp->year = (d >> 9); +} /* end of fat_get_entry_time */ + +static void fat_set_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode) +{ + u16 t, d; + DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry; + + t = (tp->hour << 11) | (tp->min << 5) | (tp->sec >> 1); + d = (tp->year << 9) | (tp->mon << 5) | tp->day; + + switch (mode) { + case TM_CREATE: + ep->create_time = cpu_to_le16(t); + ep->create_date = cpu_to_le16(d); + break; + case TM_MODIFY: + ep->modify_time = cpu_to_le16(t); + ep->modify_date = cpu_to_le16(d); + break; + } +} /* end of fat_set_entry_time */ + +static void __init_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, u32 type, u32 start_clu) +{ + TIMESTAMP_T tm, *tp; + + fat_set_entry_type((DENTRY_T *) ep, type); + ep->start_clu_lo = cpu_to_le16(CLUSTER_16(start_clu)); + ep->start_clu_hi = cpu_to_le16(CLUSTER_16(start_clu >> 16)); + ep->size = 0; + + tp = tm_now(SDFAT_SB(sb), &tm); + fat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE); + fat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY); + ep->access_date = 0; + ep->create_time_ms = 0; +} /* end of __init_dos_entry */ + +static void __init_ext_entry(EXT_DENTRY_T *ep, s32 order, u8 chksum, u16 *uniname) +{ + s32 i; + u8 end = false; + + fat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND); + ep->order = (u8) order; + ep->sysid = 0; + ep->checksum = chksum; + ep->start_clu = 0; + + /* unaligned name */ + for (i = 0; i < 5; i++) { + if (!end) { + put_unaligned_le16(*uniname, &(ep->unicode_0_4[i<<1])); + if (*uniname == 0x0) + end = true; + else + uniname++; + } else { + put_unaligned_le16(0xFFFF, &(ep->unicode_0_4[i<<1])); + } + } + + /* aligned name */ + for (i = 0; i < 6; i++) { + if (!end) { + ep->unicode_5_10[i] = cpu_to_le16(*uniname); + if (*uniname == 0x0) + end = true; + else + uniname++; + } else { + ep->unicode_5_10[i] = cpu_to_le16(0xFFFF); + } + } + + /* aligned name */ + for (i = 0; i < 2; i++) { + if (!end) { + ep->unicode_11_12[i] = cpu_to_le16(*uniname); + if (*uniname == 0x0) + end = true; + else + uniname++; + } else { + ep->unicode_11_12[i] = cpu_to_le16(0xFFFF); + } + } +} /* end of __init_ext_entry */ + +static s32 fat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type, + u32 start_clu, u64 size) +{ + u64 sector; + DOS_DENTRY_T *dos_ep; + + dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry, §or); + if (!dos_ep) + return -EIO; + + __init_dos_entry(sb, dos_ep, type, start_clu); + dcache_modify(sb, sector); + + return 0; +} /* end of fat_init_dir_entry */ + +static s32 fat_init_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 num_entries, + UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname) +{ + s32 i; + u64 sector; + u8 chksum; + u16 *uniname = p_uniname->name; + DOS_DENTRY_T *dos_ep; + EXT_DENTRY_T *ext_ep; + + dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry, §or); + if (!dos_ep) + return -EIO; + + dos_ep->lcase = p_dosname->name_case; + memcpy(dos_ep->name, p_dosname->name, DOS_NAME_LENGTH); + if (dcache_modify(sb, sector)) + return -EIO; + + if ((--num_entries) > 0) { + chksum = calc_chksum_1byte((void *) dos_ep->name, DOS_NAME_LENGTH, 0); + + for (i = 1; i < num_entries; i++) { + ext_ep = (EXT_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry-i, §or); + if (!ext_ep) + return -EIO; + + __init_ext_entry(ext_ep, i, chksum, uniname); + if (dcache_modify(sb, sector)) + return -EIO; + uniname += 13; + } + + ext_ep = (EXT_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry-i, §or); + if (!ext_ep) + return -EIO; + + __init_ext_entry(ext_ep, i+MSDOS_LAST_LFN, chksum, uniname); + if (dcache_modify(sb, sector)) + return -EIO; + } + + return 0; +} /* end of fat_init_ext_entry */ + +static s32 fat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries) +{ + s32 i; + u64 sector; + DENTRY_T *ep; + + for (i = num_entries-1; i >= order; i--) { + ep = get_dentry_in_dir(sb, p_dir, entry-i, §or); + if (!ep) + return -EIO; + + fat_set_entry_type(ep, TYPE_DELETED); + if (dcache_modify(sb, sector)) + return -EIO; + } + + return 0; +} + +/* return values of fat_find_dir_entry() + * >= 0 : return dir entiry position with the name in dir + * -EEXIST : (root dir, ".") it is the root dir itself + * -ENOENT : entry with the name does not exist + * -EIO : I/O error + */ +static inline s32 __get_dentries_per_clu(FS_INFO_T *fsi, s32 clu) +{ + if (IS_CLUS_FREE(clu)) /* FAT16 root_dir */ + return fsi->dentries_in_root; + + return fsi->dentries_per_clu; +} + +static s32 fat_find_dir_entry(struct super_block *sb, FILE_ID_T *fid, + CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *p_dosname, u32 type) +{ + s32 i, rewind = 0, dentry = 0, end_eidx = 0; + s32 chksum = 0, lfn_ord = 0, lfn_len = 0; + s32 dentries_per_clu, num_empty = 0; + u32 entry_type; + u16 entry_uniname[14], *uniname = NULL; + CHAIN_T clu; + DENTRY_T *ep; + HINT_T *hint_stat = &fid->hint_stat; + HINT_FEMP_T candi_empty; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* + * REMARK: + * DOT and DOTDOT are handled by VFS layer + */ + + dentries_per_clu = __get_dentries_per_clu(fsi, p_dir->dir); + clu.dir = p_dir->dir; + clu.flags = p_dir->flags; + + if (hint_stat->eidx) { + clu.dir = hint_stat->clu; + dentry = hint_stat->eidx; + end_eidx = dentry; + } + + candi_empty.eidx = -1; + + MMSG("lookup dir= %s\n", p_dosname->name); +rewind: + while (!IS_CLUS_EOF(clu.dir)) { + i = dentry % dentries_per_clu; + for (; i < dentries_per_clu; i++, dentry++) { + if (rewind && (dentry == end_eidx)) + goto not_found; + + ep = get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + entry_type = fat_get_entry_type(ep); + + /* + * Most directory entries have long name, + * So, we check extend directory entry first. + */ + if (entry_type == TYPE_EXTEND) { + EXT_DENTRY_T *ext_ep = (EXT_DENTRY_T *)ep; + u32 cur_ord = (u32)ext_ep->order; + u32 cur_chksum = (s32)ext_ep->checksum; + s32 len = 13; + u16 unichar; + + num_empty = 0; + candi_empty.eidx = -1; + + /* check whether new lfn or not */ + if (cur_ord & MSDOS_LAST_LFN) { + cur_ord &= ~(MSDOS_LAST_LFN); + chksum = cur_chksum; + len = (13 * (cur_ord-1)); + uniname = (p_uniname->name + len); + lfn_ord = cur_ord + 1; + lfn_len = 0; + + /* check minimum name length */ + if (cur_ord && + (len > p_uniname->name_len)) { + /* MISMATCHED NAME LENGTH */ + lfn_len = -1; + } + len = 0; + } + + /* invalid lfn order */ + if (!cur_ord || (cur_ord > MAX_LFN_ORDER) || + ((cur_ord + 1) != lfn_ord)) + goto reset_dentry_set; + + /* check checksum of directory entry set */ + if (cur_chksum != chksum) + goto reset_dentry_set; + + /* update order for next dentry */ + lfn_ord = cur_ord; + + /* check whether mismatched lfn or not */ + if (lfn_len == -1) { + /* MISMATCHED LFN DENTRY SET */ + continue; + } + + if (!uniname) { + sdfat_fs_error(sb, + "%s : abnormal dentry " + "(start_clu[%u], " + "idx[%u])", __func__, + p_dir->dir, dentry); + sdfat_debug_bug_on(1); + return -EIO; + } + + /* update position of name buffer */ + uniname -= len; + + /* get utf16 characters saved on this entry */ + len = __extract_uni_name_from_ext_entry(ext_ep, entry_uniname, lfn_ord); + + /* replace last char to null */ + unichar = *(uniname+len); + *(uniname+len) = (u16)0x0; + + /* uniname ext_dentry unit compare repeatdly */ + if (nls_cmp_uniname(sb, uniname, entry_uniname)) { + /* DO HANDLE WRONG NAME */ + lfn_len = -1; + } else { + /* add matched chars length */ + lfn_len += len; + } + + /* restore previous character */ + *(uniname+len) = unichar; + + /* jump to check next dentry */ + continue; + + } else if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) { + DOS_DENTRY_T *dos_ep = (DOS_DENTRY_T *)ep; + u32 cur_chksum = (s32)calc_chksum_1byte( + (void *) dos_ep->name, + DOS_NAME_LENGTH, 0); + + num_empty = 0; + candi_empty.eidx = -1; + + MMSG("checking dir= %c%c%c%c%c%c%c%c%c%c%c\n", + dos_ep->name[0], dos_ep->name[1], + dos_ep->name[2], dos_ep->name[3], + dos_ep->name[4], dos_ep->name[5], + dos_ep->name[6], dos_ep->name[7], + dos_ep->name[8], dos_ep->name[9], + dos_ep->name[10]); + + /* + * if there is no valid long filename, + * we should check short filename. + */ + if (!lfn_len || (cur_chksum != chksum)) { + /* check shortname */ + if ((p_dosname->name[0] != '\0') && + !nls_cmp_sfn(sb, + p_dosname->name, + dos_ep->name)) { + goto found; + } + /* check name length */ + } else if ((lfn_len > 0) && + ((s32)p_uniname->name_len == + lfn_len)) { + goto found; + } + + /* DO HANDLE MISMATCHED SFN, FALL THROUGH */ + } else if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) { + num_empty++; + if (candi_empty.eidx == -1) { + if (num_empty == 1) { + candi_empty.cur.dir = clu.dir; + candi_empty.cur.size = clu.size; + candi_empty.cur.flags = clu.flags; + } + + if (num_empty >= num_entries) { + candi_empty.eidx = dentry - (num_empty - 1); + ASSERT(0 <= candi_empty.eidx); + candi_empty.count = num_empty; + + if ((fid->hint_femp.eidx == -1) || + (candi_empty.eidx <= fid->hint_femp.eidx)) { + memcpy(&fid->hint_femp, + &candi_empty, + sizeof(HINT_FEMP_T)); + } + } + } + + if (entry_type == TYPE_UNUSED) + goto not_found; + /* FALL THROUGH */ + } +reset_dentry_set: + /* TYPE_DELETED, TYPE_VOLUME OR MISMATCHED SFN */ + lfn_ord = 0; + lfn_len = 0; + chksum = 0; + } + + if (IS_CLUS_FREE(p_dir->dir)) + break; /* FAT16 root_dir */ + + if (get_next_clus_safe(sb, &clu.dir)) + return -EIO; + } + +not_found: + /* we started at not 0 index,so we should try to find target + * from 0 index to the index we started at. + */ + if (!rewind && end_eidx) { + rewind = 1; + dentry = 0; + clu.dir = p_dir->dir; + /* reset dentry set */ + lfn_ord = 0; + lfn_len = 0; + chksum = 0; + /* reset empty hint_*/ + num_empty = 0; + candi_empty.eidx = -1; + goto rewind; + } + + /* initialized hint_stat */ + hint_stat->clu = p_dir->dir; + hint_stat->eidx = 0; + return -ENOENT; + +found: + /* next dentry we'll find is out of this cluster */ + if (!((dentry + 1) % dentries_per_clu)) { + int ret = 0; + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + clu.dir = CLUS_EOF; + else + ret = get_next_clus_safe(sb, &clu.dir); + + if (ret || IS_CLUS_EOF(clu.dir)) { + /* just initialized hint_stat */ + hint_stat->clu = p_dir->dir; + hint_stat->eidx = 0; + return dentry; + } + } + + hint_stat->clu = clu.dir; + hint_stat->eidx = dentry + 1; + return dentry; +} /* end of fat_find_dir_entry */ + +/* returns -EIO on error */ +static s32 fat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry) +{ + s32 count = 0; + u8 chksum; + DOS_DENTRY_T *dos_ep = (DOS_DENTRY_T *) p_entry; + EXT_DENTRY_T *ext_ep; + + chksum = calc_chksum_1byte((void *) dos_ep->name, DOS_NAME_LENGTH, 0); + + for (entry--; entry >= 0; entry--) { + ext_ep = (EXT_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, NULL); + if (!ext_ep) + return -EIO; + + if ((fat_get_entry_type((DENTRY_T *)ext_ep) == TYPE_EXTEND) && + (ext_ep->checksum == chksum)) { + count++; + if (ext_ep->order > MSDOS_LAST_LFN) + return count; + } else { + return count; + } + } + + return count; +} + + +/* + * Name Conversion Functions + */ +static s32 __extract_uni_name_from_ext_entry(EXT_DENTRY_T *ep, u16 *uniname, s32 order) +{ + s32 i, len = 0; + + for (i = 0; i < 5; i++) { + *uniname = get_unaligned_le16(&(ep->unicode_0_4[i<<1])); + if (*uniname == 0x0) + return len; + uniname++; + len++; + } + + if (order < 20) { + for (i = 0; i < 6; i++) { + /* FIXME : unaligned? */ + *uniname = le16_to_cpu(ep->unicode_5_10[i]); + if (*uniname == 0x0) + return len; + uniname++; + len++; + } + } else { + for (i = 0; i < 4; i++) { + /* FIXME : unaligned? */ + *uniname = le16_to_cpu(ep->unicode_5_10[i]); + if (*uniname == 0x0) + return len; + uniname++; + len++; + } + *uniname = 0x0; /* uniname[MAX_NAME_LENGTH] */ + return len; + } + + for (i = 0; i < 2; i++) { + /* FIXME : unaligned? */ + *uniname = le16_to_cpu(ep->unicode_11_12[i]); + if (*uniname == 0x0) + return len; + uniname++; + len++; + } + + *uniname = 0x0; + return len; + +} /* end of __extract_uni_name_from_ext_entry */ + +static void fat_get_uniname_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname) +{ + u32 i; + u16 *name = uniname; + u32 chksum; + + DOS_DENTRY_T *dos_ep = + (DOS_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, NULL); + + if (unlikely(!dos_ep)) + goto invalid_lfn; + + chksum = (u32)calc_chksum_1byte( + (void *) dos_ep->name, + DOS_NAME_LENGTH, 0); + + for (entry--, i = 1; entry >= 0; entry--, i++) { + EXT_DENTRY_T *ep; + + ep = (EXT_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, NULL); + if (!ep) + goto invalid_lfn; + + if (fat_get_entry_type((DENTRY_T *) ep) != TYPE_EXTEND) + goto invalid_lfn; + + if (chksum != (u32)ep->checksum) + goto invalid_lfn; + + if (i != (u32)(ep->order & ~(MSDOS_LAST_LFN))) + goto invalid_lfn; + + __extract_uni_name_from_ext_entry(ep, name, (s32)i); + if (ep->order & MSDOS_LAST_LFN) + return; + + name += 13; + } +invalid_lfn: + *uniname = (u16)0x0; +} /* end of fat_get_uniname_from_ext_entry */ + +/* Find if the shortname exists + * and check if there are free entries + */ +static s32 __fat_find_shortname_entry(struct super_block *sb, CHAIN_T *p_dir, + u8 *p_dosname, s32 *offset, __attribute__((unused))int n_entry_needed) +{ + u32 type; + s32 i, dentry = 0; + s32 dentries_per_clu; + DENTRY_T *ep = NULL; + DOS_DENTRY_T *dos_ep = NULL; + CHAIN_T clu = *p_dir; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (offset) + *offset = -1; + + if (IS_CLUS_FREE(clu.dir)) /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + else + dentries_per_clu = fsi->dentries_per_clu; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < dentries_per_clu; i++, dentry++) { + ep = get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = fat_get_entry_type(ep); + + if ((type == TYPE_FILE) || (type == TYPE_DIR)) { + dos_ep = (DOS_DENTRY_T *)ep; + if (!nls_cmp_sfn(sb, p_dosname, dos_ep->name)) { + if (offset) + *offset = dentry; + return 0; + } + } + } + + /* fat12/16 root dir */ + if (IS_CLUS_FREE(clu.dir)) + break; + + if (get_next_clus_safe(sb, &clu.dir)) + return -EIO; + } + return -ENOENT; +} + +#ifdef CONFIG_SDFAT_FAT32_SHORTNAME_SEQ +static void __fat_attach_count_to_dos_name(u8 *dosname, s32 count) +{ + s32 i, j, length; + s8 str_count[6]; + + snprintf(str_count, sizeof(str_count), "~%d", count); + length = strlen(str_count); + + i = j = 0; + while (j <= (8 - length)) { + i = j; + if (dosname[j] == ' ') + break; + if (dosname[j] & 0x80) + j += 2; + else + j++; + } + + for (j = 0; j < length; i++, j++) + dosname[i] = (u8) str_count[j]; + + if (i == 7) + dosname[7] = ' '; + +} /* end of __fat_attach_count_to_dos_name */ +#endif + +s32 fat_generate_dos_name_new(struct super_block *sb, CHAIN_T *p_dir, DOS_NAME_T *p_dosname, s32 n_entry_needed) +{ + s32 i; + s32 baselen, err; + u8 work[DOS_NAME_LENGTH], buf[5]; + u8 tail; + + baselen = 8; + memset(work, ' ', DOS_NAME_LENGTH); + memcpy(work, p_dosname->name, DOS_NAME_LENGTH); + + while (baselen && (work[--baselen] == ' ')) { + /* DO NOTHING, JUST FOR CHECK_PATCH */ + } + + if (baselen > 6) + baselen = 6; + + BUG_ON(baselen < 0); + +#ifdef CONFIG_SDFAT_FAT32_SHORTNAME_SEQ + /* example) namei_exfat.c -> NAMEI_~1 - NAMEI_~9 */ + work[baselen] = '~'; + for (i = 1; i < 10; i++) { + // '0' + i = 1 ~ 9 ASCII + work[baselen + 1] = '0' + i; + err = __fat_find_shortname_entry(sb, p_dir, work, NULL, n_entry_needed); + if (err == -ENOENT) { + /* void return */ + __fat_attach_count_to_dos_name(p_dosname->name, i); + return 0; + } + + /* any other error */ + if (err) + return err; + } +#endif + + i = jiffies; + tail = (jiffies >> 16) & 0x7; + + if (baselen > 2) + baselen = 2; + + BUG_ON(baselen < 0); + + work[baselen + 4] = '~'; + // 1 ~ 8 ASCII + work[baselen + 5] = '1' + tail; + while (1) { + snprintf(buf, sizeof(buf), "%04X", i & 0xffff); + memcpy(&work[baselen], buf, 4); + err = __fat_find_shortname_entry(sb, p_dir, work, NULL, n_entry_needed); + if (err == -ENOENT) { + memcpy(p_dosname->name, work, DOS_NAME_LENGTH); + break; + } + + /* any other error */ + if (err) + return err; + + i -= 11; + } + return 0; +} /* end of generate_dos_name_new */ + +static s32 fat_calc_num_entries(UNI_NAME_T *p_uniname) +{ + s32 len; + + len = p_uniname->name_len; + if (len == 0) + return 0; + + /* 1 dos name entry + extended entries */ + return((len-1) / 13 + 2); + +} /* end of calc_num_enties */ + +static s32 fat_check_max_dentries(FILE_ID_T *fid) +{ + if ((fid->size >> DENTRY_SIZE_BITS) >= MAX_FAT_DENTRIES) { + /* FAT spec allows a dir to grow upto 65536 dentries */ + return -ENOSPC; + } + return 0; +} /* end of check_max_dentries */ + + +/* + * File Operation Functions + */ +static FS_FUNC_T fat_fs_func = { + .alloc_cluster = fat_alloc_cluster, + .free_cluster = fat_free_cluster, + .count_used_clusters = fat_count_used_clusters, + + .init_dir_entry = fat_init_dir_entry, + .init_ext_entry = fat_init_ext_entry, + .find_dir_entry = fat_find_dir_entry, + .delete_dir_entry = fat_delete_dir_entry, + .get_uniname_from_ext_entry = fat_get_uniname_from_ext_entry, + .count_ext_entries = fat_count_ext_entries, + .calc_num_entries = fat_calc_num_entries, + .check_max_dentries = fat_check_max_dentries, + + .get_entry_type = fat_get_entry_type, + .set_entry_type = fat_set_entry_type, + .get_entry_attr = fat_get_entry_attr, + .set_entry_attr = fat_set_entry_attr, + .get_entry_flag = fat_get_entry_flag, + .set_entry_flag = fat_set_entry_flag, + .get_entry_clu0 = fat_get_entry_clu0, + .set_entry_clu0 = fat_set_entry_clu0, + .get_entry_size = fat_get_entry_size, + .set_entry_size = fat_set_entry_size, + .get_entry_time = fat_get_entry_time, + .set_entry_time = fat_set_entry_time, +}; + +static FS_FUNC_T amap_fat_fs_func = { + .alloc_cluster = amap_fat_alloc_cluster, + .free_cluster = fat_free_cluster, + .count_used_clusters = fat_count_used_clusters, + + .init_dir_entry = fat_init_dir_entry, + .init_ext_entry = fat_init_ext_entry, + .find_dir_entry = fat_find_dir_entry, + .delete_dir_entry = fat_delete_dir_entry, + .get_uniname_from_ext_entry = fat_get_uniname_from_ext_entry, + .count_ext_entries = fat_count_ext_entries, + .calc_num_entries = fat_calc_num_entries, + .check_max_dentries = fat_check_max_dentries, + + .get_entry_type = fat_get_entry_type, + .set_entry_type = fat_set_entry_type, + .get_entry_attr = fat_get_entry_attr, + .set_entry_attr = fat_set_entry_attr, + .get_entry_flag = fat_get_entry_flag, + .set_entry_flag = fat_set_entry_flag, + .get_entry_clu0 = fat_get_entry_clu0, + .set_entry_clu0 = fat_set_entry_clu0, + .get_entry_size = fat_get_entry_size, + .set_entry_size = fat_set_entry_size, + .get_entry_time = fat_get_entry_time, + .set_entry_time = fat_set_entry_time, + + .get_au_stat = amap_get_au_stat, +}; + +s32 mount_fat16(struct super_block *sb, pbr_t *p_pbr) +{ + s32 num_root_sectors; + bpb16_t *p_bpb = &(p_pbr->bpb.f16); + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (!p_bpb->num_fats) { + sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure"); + return -EINVAL; + } + + num_root_sectors = get_unaligned_le16(p_bpb->num_root_entries) << DENTRY_SIZE_BITS; + num_root_sectors = ((num_root_sectors-1) >> sb->s_blocksize_bits) + 1; + + fsi->sect_per_clus = p_bpb->sect_per_clus; + fsi->sect_per_clus_bits = ilog2(p_bpb->sect_per_clus); + fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits; + fsi->cluster_size = 1 << fsi->cluster_size_bits; + + fsi->num_FAT_sectors = le16_to_cpu(p_bpb->num_fat_sectors); + + fsi->FAT1_start_sector = le16_to_cpu(p_bpb->num_reserved); + if (p_bpb->num_fats == 1) + fsi->FAT2_start_sector = fsi->FAT1_start_sector; + else + fsi->FAT2_start_sector = fsi->FAT1_start_sector + fsi->num_FAT_sectors; + + fsi->root_start_sector = fsi->FAT2_start_sector + fsi->num_FAT_sectors; + fsi->data_start_sector = fsi->root_start_sector + num_root_sectors; + + fsi->num_sectors = get_unaligned_le16(p_bpb->num_sectors); + if (!fsi->num_sectors) + fsi->num_sectors = le32_to_cpu(p_bpb->num_huge_sectors); + + if (!fsi->num_sectors) { + sdfat_msg(sb, KERN_ERR, "bogus number of total sector count"); + return -EINVAL; + } + + fsi->num_clusters = (u32)((fsi->num_sectors - fsi->data_start_sector) >> fsi->sect_per_clus_bits) + CLUS_BASE; + /* because the cluster index starts with 2 */ + + fsi->vol_type = FAT16; + if (fsi->num_clusters < FAT12_THRESHOLD) + fsi->vol_type = FAT12; + + fsi->vol_id = get_unaligned_le32(p_bpb->vol_serial); + + fsi->root_dir = 0; + fsi->dentries_in_root = get_unaligned_le16(p_bpb->num_root_entries); + if (!fsi->dentries_in_root) { + sdfat_msg(sb, KERN_ERR, "bogus number of max dentry count " + "of the root directory"); + return -EINVAL; + } + + fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS); + + fsi->vol_flag = VOL_CLEAN; + fsi->clu_srch_ptr = 2; + fsi->used_clusters = (u32) ~0; + + fsi->fs_func = &fat_fs_func; + fat_ent_ops_init(sb); + + if (p_bpb->state & FAT_VOL_DIRTY) { + fsi->vol_flag |= VOL_DIRTY; + sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly " + "unmounted. Some data may be corrupt. " + "Please run fsck."); + } + + return 0; +} /* end of mount_fat16 */ + +static sector_t __calc_hidden_sect(struct super_block *sb) +{ + struct block_device *bdev = sb->s_bdev; + sector_t hidden = 0; + + if (!bdev) + goto out; + + hidden = bdev->bd_part->start_sect; + /* a disk device, not a partition */ + if (!hidden) { + ASSERT(bdev == bdev->bd_contains); + ASSERT(!bdev->bd_part->partno); + goto out; + } + + if (sb->s_blocksize_bits != 9) { + ASSERT(sb->s_blocksize_bits > 9); + hidden >>= (sb->s_blocksize_bits - 9); + } + +out: + sdfat_log_msg(sb, KERN_INFO, "start_sect of partition : %lld", + (s64)hidden); + return hidden; + +} + +s32 mount_fat32(struct super_block *sb, pbr_t *p_pbr) +{ + pbr32_t *p_bpb = (pbr32_t *)p_pbr; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (!p_bpb->bpb.num_fats) { + sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure"); + return -EINVAL; + } + + fsi->sect_per_clus = p_bpb->bpb.sect_per_clus; + fsi->sect_per_clus_bits = ilog2(p_bpb->bpb.sect_per_clus); + fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits; + fsi->cluster_size = 1 << fsi->cluster_size_bits; + + fsi->num_FAT_sectors = le32_to_cpu(p_bpb->bpb.num_fat32_sectors); + + fsi->FAT1_start_sector = le16_to_cpu(p_bpb->bpb.num_reserved); + if (p_bpb->bpb.num_fats == 1) + fsi->FAT2_start_sector = fsi->FAT1_start_sector; + else + fsi->FAT2_start_sector = fsi->FAT1_start_sector + fsi->num_FAT_sectors; + + fsi->root_start_sector = fsi->FAT2_start_sector + fsi->num_FAT_sectors; + fsi->data_start_sector = fsi->root_start_sector; + + /* SPEC violation for compatibility */ + fsi->num_sectors = get_unaligned_le16(p_bpb->bpb.num_sectors); + if (!fsi->num_sectors) + fsi->num_sectors = le32_to_cpu(p_bpb->bpb.num_huge_sectors); + + /* 2nd check */ + if (!fsi->num_sectors) { + sdfat_msg(sb, KERN_ERR, "bogus number of total sector count"); + return -EINVAL; + } + + fsi->num_clusters = (u32)((fsi->num_sectors - fsi->data_start_sector) >> fsi->sect_per_clus_bits) + CLUS_BASE; + /* because the cluster index starts with 2 */ + + fsi->vol_type = FAT32; + fsi->vol_id = get_unaligned_le32(p_bpb->bsx.vol_serial); + + fsi->root_dir = le32_to_cpu(p_bpb->bpb.root_cluster); + fsi->dentries_in_root = 0; + fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS); + + fsi->vol_flag = VOL_CLEAN; + fsi->clu_srch_ptr = 2; + fsi->used_clusters = (u32) ~0; + + fsi->fs_func = &fat_fs_func; + + /* Delayed / smart allocation related init */ + fsi->reserved_clusters = 0; + + /* Should be initialized before calling amap_create() */ + fat_ent_ops_init(sb); + + /* AU Map Creation */ + if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART) { + u32 hidden_sectors = le32_to_cpu(p_bpb->bpb.num_hid_sectors); + u32 calc_hid_sect = 0; + int ret; + + + /* calculate hidden sector size */ + calc_hid_sect = __calc_hidden_sect(sb); + if (calc_hid_sect != hidden_sectors) { + sdfat_log_msg(sb, KERN_WARNING, "abnormal hidden " + "sector : bpb(%u) != ondisk(%u)", + hidden_sectors, calc_hid_sect); + if (SDFAT_SB(sb)->options.adj_hidsect) { + sdfat_log_msg(sb, KERN_INFO, + "adjustment hidden sector : " + "bpb(%u) -> ondisk(%u)", + hidden_sectors, calc_hid_sect); + hidden_sectors = calc_hid_sect; + } + } + + SDFAT_SB(sb)->options.amap_opt.misaligned_sect = hidden_sectors; + + /* calculate AU size if it's not set */ + if (!SDFAT_SB(sb)->options.amap_opt.sect_per_au) { + SDFAT_SB(sb)->options.amap_opt.sect_per_au = + __calc_default_au_size(sb); + } + + ret = amap_create(sb, + SDFAT_SB(sb)->options.amap_opt.pack_ratio, + SDFAT_SB(sb)->options.amap_opt.sect_per_au, + SDFAT_SB(sb)->options.amap_opt.misaligned_sect); + if (ret) { + sdfat_log_msg(sb, KERN_WARNING, "failed to create AMAP." + " disabling smart allocation. (err:%d)", ret); + SDFAT_SB(sb)->options.improved_allocation &= ~(SDFAT_ALLOC_SMART); + } else { + fsi->fs_func = &amap_fat_fs_func; + } + } + + /* Check dependency of mount options */ + if (SDFAT_SB(sb)->options.improved_allocation != + (SDFAT_ALLOC_DELAY | SDFAT_ALLOC_SMART)) { + sdfat_log_msg(sb, KERN_INFO, "disabling defragmentation because" + " smart, delay options are disabled"); + SDFAT_SB(sb)->options.defrag = 0; + } + + if (p_bpb->bsx.state & FAT_VOL_DIRTY) { + fsi->vol_flag |= VOL_DIRTY; + sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly " + "unmounted. Some data may be corrupt. " + "Please run fsck."); + } + + return 0; +} /* end of mount_fat32 */ + +/* end of core_fat.c */ diff --git a/fs/sdfat/dfr.c b/fs/sdfat/dfr.c new file mode 100644 index 0000000000000..2853fe4251978 --- /dev/null +++ b/fs/sdfat/dfr.c @@ -0,0 +1,1372 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* @PROJECT : exFAT & FAT12/16/32 File System */ +/* @FILE : dfr.c */ +/* @PURPOSE : Defragmentation support for SDFAT32 */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include + +#include "sdfat.h" +#include "core.h" +#include "amap_smart.h" + +#ifdef CONFIG_SDFAT_DFR +/** + * @fn defrag_get_info + * @brief get HW params for defrag daemon + * @return 0 on success, -errno otherwise + * @param sb super block + * @param arg defrag info arguments + * @remark protected by super_block + */ +int +defrag_get_info( + IN struct super_block *sb, + OUT struct defrag_info_arg *arg) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + + if (!arg) + return -EINVAL; + + arg->sec_sz = sb->s_blocksize; + arg->clus_sz = fsi->cluster_size; + arg->total_sec = fsi->num_sectors; + arg->fat_offset_sec = fsi->FAT1_start_sector; + arg->fat_sz_sec = fsi->num_FAT_sectors; + arg->n_fat = (fsi->FAT1_start_sector == fsi->FAT2_start_sector) ? 1 : 2; + + arg->sec_per_au = amap->option.au_size; + arg->hidden_sectors = amap->option.au_align_factor % amap->option.au_size; + + return 0; +} + + +static int +__defrag_scan_dir( + IN struct super_block *sb, + IN DOS_DENTRY_T *dos_ep, + IN loff_t i_pos, + OUT struct defrag_trav_arg *arg) +{ + FS_INFO_T *fsi = NULL; + UNI_NAME_T uniname; + unsigned int type = 0, start_clus = 0; + int err = -EPERM; + + /* Check params */ + ERR_HANDLE2((!sb || !dos_ep || !i_pos || !arg), err, -EINVAL); + fsi = &(SDFAT_SB(sb)->fsi); + + /* Get given entry's type */ + type = fsi->fs_func->get_entry_type((DENTRY_T *) dos_ep); + + /* Check dos_ep */ + if (!strncmp(dos_ep->name, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) { + ; + } else if (!strncmp(dos_ep->name, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH)) { + ; + } else if ((type == TYPE_DIR) || (type == TYPE_FILE)) { + + /* Set start_clus */ + SET32_HI(start_clus, le16_to_cpu(dos_ep->start_clu_hi)); + SET32_LO(start_clus, le16_to_cpu(dos_ep->start_clu_lo)); + arg->start_clus = start_clus; + + /* Set type & i_pos */ + if (type == TYPE_DIR) + arg->type = DFR_TRAV_TYPE_DIR; + else + arg->type = DFR_TRAV_TYPE_FILE; + + arg->i_pos = i_pos; + + /* Set name */ + memset(&uniname, 0, sizeof(UNI_NAME_T)); + get_uniname_from_dos_entry(sb, dos_ep, &uniname, 0x1); + /* FIXME : + * we should think that whether the size of arg->name + * is enough or not + */ + nls_uni16s_to_vfsname(sb, &uniname, + arg->name, sizeof(arg->name)); + + err = 0; + /* End case */ + } else if (type == TYPE_UNUSED) { + err = -ENOENT; + } else { + ; + } + +error: + return err; +} + + +/** + * @fn defrag_scan_dir + * @brief scan given directory + * @return 0 on success, -errno otherwise + * @param sb super block + * @param args traverse args + * @remark protected by inode_lock, super_block and volume lock + */ +int +defrag_scan_dir( + IN struct super_block *sb, + INOUT struct defrag_trav_arg *args) +{ + struct sdfat_sb_info *sbi = NULL; + FS_INFO_T *fsi = NULL; + struct defrag_trav_header *header = NULL; + DOS_DENTRY_T *dos_ep; + CHAIN_T chain; + int dot_found = 0, args_idx = DFR_TRAV_HEADER_IDX + 1, clus = 0, index = 0; + int err = 0, j = 0; + + /* Check params */ + ERR_HANDLE2((!sb || !args), err, -EINVAL); + sbi = SDFAT_SB(sb); + fsi = &(sbi->fsi); + header = (struct defrag_trav_header *) args; + + /* Exceptional case for ROOT */ + if (header->i_pos == DFR_TRAV_ROOT_IPOS) { + header->start_clus = fsi->root_dir; + dfr_debug("IOC_DFR_TRAV for ROOT: start_clus %08x", header->start_clus); + dot_found = 1; + } + + chain.dir = header->start_clus; + chain.size = 0; + chain.flags = 0; + + /* Check if this is directory */ + if (!dot_found) { + FAT32_CHECK_CLUSTER(fsi, chain.dir, err); + ERR_HANDLE(err); + dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, &chain, 0, NULL); + ERR_HANDLE2(!dos_ep, err, -EIO); + + if (strncmp(dos_ep->name, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) { + err = -EINVAL; + dfr_err("Scan: Not a directory, err %d", err); + goto error; + } + } + + /* For more-scan case */ + if ((header->stat == DFR_TRAV_STAT_MORE) && + (header->start_clus == sbi->dfr_hint_clus) && + (sbi->dfr_hint_idx > 0)) { + + index = sbi->dfr_hint_idx; + for (j = 0; j < (sbi->dfr_hint_idx / fsi->dentries_per_clu); j++) { + /* Follow FAT-chain */ + FAT32_CHECK_CLUSTER(fsi, chain.dir, err); + ERR_HANDLE(err); + err = fat_ent_get(sb, chain.dir, &(chain.dir)); + ERR_HANDLE(err); + + if (!IS_CLUS_EOF(chain.dir)) { + clus++; + index -= fsi->dentries_per_clu; + } else { + /** + * This directory modified. Stop scanning. + */ + err = -EINVAL; + dfr_err("Scan: SCAN_MORE failed, err %d", err); + goto error; + } + } + + /* For first-scan case */ + } else { + clus = 0; + index = 0; + } + +scan_fat_chain: + /* Scan given directory and get info of children */ + for ( ; index < fsi->dentries_per_clu; index++) { + DOS_DENTRY_T *dos_ep = NULL; + loff_t i_pos = 0; + + /* Get dos_ep */ + FAT32_CHECK_CLUSTER(fsi, chain.dir, err); + ERR_HANDLE(err); + dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, &chain, index, NULL); + ERR_HANDLE2(!dos_ep, err, -EIO); + + /* Make i_pos for this entry */ + SET64_HI(i_pos, header->start_clus); + SET64_LO(i_pos, clus * fsi->dentries_per_clu + index); + + err = __defrag_scan_dir(sb, dos_ep, i_pos, &args[args_idx]); + if (!err) { + /* More-scan case */ + if (++args_idx >= (PAGE_SIZE / sizeof(struct defrag_trav_arg))) { + sbi->dfr_hint_clus = header->start_clus; + sbi->dfr_hint_idx = clus * fsi->dentries_per_clu + index + 1; + + header->stat = DFR_TRAV_STAT_MORE; + header->nr_entries = args_idx; + goto error; + } + /* Error case */ + } else if (err == -EINVAL) { + sbi->dfr_hint_clus = sbi->dfr_hint_idx = 0; + dfr_err("Scan: err %d", err); + goto error; + /* End case */ + } else if (err == -ENOENT) { + sbi->dfr_hint_clus = sbi->dfr_hint_idx = 0; + err = 0; + goto done; + } else { + /* DO NOTHING */ + } + err = 0; + } + + /* Follow FAT-chain */ + FAT32_CHECK_CLUSTER(fsi, chain.dir, err); + ERR_HANDLE(err); + err = fat_ent_get(sb, chain.dir, &(chain.dir)); + ERR_HANDLE(err); + + if (!IS_CLUS_EOF(chain.dir)) { + index = 0; + clus++; + goto scan_fat_chain; + } + +done: + /* Update header */ + header->stat = DFR_TRAV_STAT_DONE; + header->nr_entries = args_idx; + +error: + return err; +} + + +static int +__defrag_validate_cluster_prev( + IN struct super_block *sb, + IN struct defrag_chunk_info *chunk) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + CHAIN_T dir; + DENTRY_T *ep = NULL; + unsigned int entry = 0, clus = 0; + int err = 0; + + if (chunk->prev_clus == 0) { + /* For the first cluster of a file */ + dir.dir = GET64_HI(chunk->i_pos); + dir.flags = 0x1; // Assume non-continuous + + entry = GET64_LO(chunk->i_pos); + + FAT32_CHECK_CLUSTER(fsi, dir.dir, err); + ERR_HANDLE(err); + ep = get_dentry_in_dir(sb, &dir, entry, NULL); + if (!ep) { + err = -EPERM; + goto error; + } + + /* should call fat_get_entry_clu0(ep) */ + clus = fsi->fs_func->get_entry_clu0(ep); + if (clus != chunk->d_clus) { + err = -ENXIO; + goto error; + } + } else { + /* Normal case */ + FAT32_CHECK_CLUSTER(fsi, chunk->prev_clus, err); + ERR_HANDLE(err); + err = fat_ent_get(sb, chunk->prev_clus, &clus); + if (err) + goto error; + if (chunk->d_clus != clus) + err = -ENXIO; + } + +error: + return err; +} + + +static int +__defrag_validate_cluster_next( + IN struct super_block *sb, + IN struct defrag_chunk_info *chunk) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + unsigned int clus = 0; + int err = 0; + + /* Check next_clus */ + FAT32_CHECK_CLUSTER(fsi, (chunk->d_clus + chunk->nr_clus - 1), err); + ERR_HANDLE(err); + err = fat_ent_get(sb, (chunk->d_clus + chunk->nr_clus - 1), &clus); + if (err) + goto error; + if (chunk->next_clus != (clus & FAT32_EOF)) + err = -ENXIO; + +error: + return err; +} + + +/** + * @fn __defrag_check_au + * @brief check if this AU is in use + * @return 0 if idle, 1 if busy + * @param sb super block + * @param clus physical cluster num + * @param limit # of used clusters from daemon + */ +static int +__defrag_check_au( + struct super_block *sb, + u32 clus, + u32 limit) +{ + unsigned int nr_free = amap_get_freeclus(sb, clus); + +#if defined(CONFIG_SDFAT_DFR_DEBUG) && defined(CONFIG_SDFAT_DBG_MSG) + if (nr_free < limit) { + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + AU_INFO_T *au = GET_AU(amap, i_AU_of_CLU(amap, clus)); + + dfr_debug("AU[%d] nr_free %d, limit %d", au->idx, nr_free, limit); + } +#endif + return ((nr_free < limit) ? 1 : 0); +} + + +/** + * @fn defrag_validate_cluster + * @brief validate cluster info of given chunk + * @return 0 on success, -errno otherwise + * @param inode inode of given chunk + * @param chunk given chunk + * @param skip_prev flag to skip checking previous cluster info + * @remark protected by super_block and volume lock + */ +int +defrag_validate_cluster( + IN struct inode *inode, + IN struct defrag_chunk_info *chunk, + IN int skip_prev) +{ + struct super_block *sb = inode->i_sb; + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + unsigned int clus = 0; + int err = 0, i = 0; + + /* If this inode is unlink-ed, skip it */ + if (fid->dir.dir == DIR_DELETED) + return -ENOENT; + + /* Skip working-AU */ + err = amap_check_working(sb, chunk->d_clus); + if (err) + return -EBUSY; + + /* Check # of free_clus of belonged AU */ + err = __defrag_check_au(inode->i_sb, chunk->d_clus, CLUS_PER_AU(sb) - chunk->au_clus); + if (err) + return -EINVAL; + + /* Check chunk's clusters */ + for (i = 0; i < chunk->nr_clus; i++) { + err = fsapi_map_clus(inode, chunk->f_clus + i, &clus, ALLOC_NOWHERE); + if (err || (chunk->d_clus + i != clus)) { + if (!err) + err = -ENXIO; + goto error; + } + } + + /* Check next_clus */ + err = __defrag_validate_cluster_next(sb, chunk); + ERR_HANDLE(err); + + if (!skip_prev) { + /* Check prev_clus */ + err = __defrag_validate_cluster_prev(sb, chunk); + ERR_HANDLE(err); + } + +error: + return err; +} + + +/** + * @fn defrag_reserve_clusters + * @brief reserve clusters for defrag + * @return 0 on success, -errno otherwise + * @param sb super block + * @param nr_clus # of clusters to reserve + * @remark protected by super_block and volume lock + */ +int +defrag_reserve_clusters( + INOUT struct super_block *sb, + IN int nr_clus) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + + if (!(sbi->options.improved_allocation & SDFAT_ALLOC_DELAY)) + /* Nothing to do */ + return 0; + + /* Check error case */ + if (fsi->used_clusters + fsi->reserved_clusters + nr_clus >= fsi->num_clusters - 2) { + return -ENOSPC; + } else if (fsi->reserved_clusters + nr_clus < 0) { + dfr_err("Reserve count: reserved_clusters %d, nr_clus %d", + fsi->reserved_clusters, nr_clus); + BUG_ON(fsi->reserved_clusters + nr_clus < 0); + } + + sbi->dfr_reserved_clus += nr_clus; + fsi->reserved_clusters += nr_clus; + + return 0; +} + + +/** + * @fn defrag_mark_ignore + * @brief mark corresponding AU to be ignored + * @return 0 on success, -errno otherwise + * @param sb super block + * @param clus given cluster num + * @remark protected by super_block + */ +int +defrag_mark_ignore( + INOUT struct super_block *sb, + IN unsigned int clus) +{ + int err = 0; + + if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART) + err = amap_mark_ignore(sb, clus); + + if (err) + dfr_debug("err %d", err); + return err; +} + + +/** + * @fn defrag_unmark_ignore_all + * @brief unmark all ignored AUs + * @return void + * @param sb super block + * @remark protected by super_block + */ +void +defrag_unmark_ignore_all(struct super_block *sb) +{ + if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART) + amap_unmark_ignore_all(sb); +} + + +/** + * @fn defrag_map_cluster + * @brief get_block function for defrag dests + * @return 0 on success, -errno otherwise + * @param inode inode + * @param clu_offset logical cluster offset + * @param clu mapped cluster (physical) + * @remark protected by super_block and volume lock + */ +int +defrag_map_cluster( + struct inode *inode, + unsigned int clu_offset, + unsigned int *clu) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); +#ifdef CONFIG_SDFAT_DFR_PACKING + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; +#endif + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + struct defrag_info *ino_dfr = &(SDFAT_I(inode)->dfr_info); + struct defrag_chunk_info *chunk = NULL; + CHAIN_T new_clu; + int i = 0, nr_new = 0, err = 0; + + /* Get corresponding chunk */ + for (i = 0; i < ino_dfr->nr_chunks; i++) { + chunk = &(ino_dfr->chunks[i]); + + if ((chunk->f_clus <= clu_offset) && (clu_offset < chunk->f_clus + chunk->nr_clus)) { + /* For already allocated new_clus */ + if (sbi->dfr_new_clus[chunk->new_idx + clu_offset - chunk->f_clus]) { + *clu = sbi->dfr_new_clus[chunk->new_idx + clu_offset - chunk->f_clus]; + return 0; + } + break; + } + } + BUG_ON(!chunk); + + fscore_set_vol_flags(sb, VOL_DIRTY, 0); + + new_clu.dir = CLUS_EOF; + new_clu.size = 0; + new_clu.flags = fid->flags; + + /* Allocate new cluster */ +#ifdef CONFIG_SDFAT_DFR_PACKING + if (amap->n_clean_au * DFR_FULL_RATIO <= amap->n_au * DFR_DEFAULT_PACKING_RATIO) + err = fsi->fs_func->alloc_cluster(sb, 1, &new_clu, ALLOC_COLD_PACKING); + else + err = fsi->fs_func->alloc_cluster(sb, 1, &new_clu, ALLOC_COLD_ALIGNED); +#else + err = fsi->fs_func->alloc_cluster(sb, 1, &new_clu, ALLOC_COLD_ALIGNED); +#endif + + if (err) { + dfr_err("Map: 1 %d", 0); + return err; + } + + /* Decrease reserved cluster count */ + defrag_reserve_clusters(sb, -1); + + /* Add new_clus info in ino_dfr */ + sbi->dfr_new_clus[chunk->new_idx + clu_offset - chunk->f_clus] = new_clu.dir; + + /* Make FAT-chain for new_clus */ + for (i = 0; i < chunk->nr_clus; i++) { +#if 0 + if (sbi->dfr_new_clus[chunk->new_idx + i]) + nr_new++; + else + break; +#else + if (!sbi->dfr_new_clus[chunk->new_idx + i]) + break; + nr_new++; +#endif + } + if (nr_new == chunk->nr_clus) { + for (i = 0; i < chunk->nr_clus - 1; i++) { + FAT32_CHECK_CLUSTER(fsi, sbi->dfr_new_clus[chunk->new_idx + i], err); + BUG_ON(err); + if (fat_ent_set(sb, + sbi->dfr_new_clus[chunk->new_idx + i], + sbi->dfr_new_clus[chunk->new_idx + i + 1])) + return -EIO; + } + } + + *clu = new_clu.dir; + return 0; +} + + +/** + * @fn defrag_writepage_end_io + * @brief check WB status of requested page + * @return void + * @param page page + */ +void +defrag_writepage_end_io( + INOUT struct page *page) +{ + struct super_block *sb = page->mapping->host->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct defrag_info *ino_dfr = &(SDFAT_I(page->mapping->host)->dfr_info); + unsigned int clus_start = 0, clus_end = 0; + int i = 0; + + /* Check if this inode is on defrag */ + if (atomic_read(&ino_dfr->stat) != DFR_INO_STAT_REQ) + return; + + clus_start = page->index / PAGES_PER_CLUS(sb); + clus_end = clus_start + 1; + + /* Check each chunk in given inode */ + for (i = 0; i < ino_dfr->nr_chunks; i++) { + struct defrag_chunk_info *chunk = &(ino_dfr->chunks[i]); + unsigned int chunk_start = 0, chunk_end = 0; + + chunk_start = chunk->f_clus; + chunk_end = chunk->f_clus + chunk->nr_clus; + + if ((clus_start >= chunk_start) && (clus_end <= chunk_end)) { + int off = clus_start - chunk_start; + + clear_bit((page->index & (PAGES_PER_CLUS(sb) - 1)), + (volatile unsigned long *)&(sbi->dfr_page_wb[chunk->new_idx + off])); + } + } +} + + +/** + * @fn __defrag_check_wb + * @brief check if WB for given chunk completed + * @return 0 on success, -errno otherwise + * @param sbi super block info + * @param chunk given chunk + */ +static int +__defrag_check_wb( + IN struct sdfat_sb_info *sbi, + IN struct defrag_chunk_info *chunk) +{ + int err = 0, wb_i = 0, i = 0, nr_new = 0; + + if (!sbi || !chunk) + return -EINVAL; + + /* Check WB complete status first */ + for (wb_i = 0; wb_i < chunk->nr_clus; wb_i++) { + if (atomic_read((atomic_t *)&(sbi->dfr_page_wb[chunk->new_idx + wb_i]))) { + err = -EBUSY; + break; + } + } + + /** + * Check NEW_CLUS status. + * writepage_end_io cannot check whole WB complete status, + * so we need to check NEW_CLUS status. + */ + for (i = 0; i < chunk->nr_clus; i++) + if (sbi->dfr_new_clus[chunk->new_idx + i]) + nr_new++; + + if (nr_new == chunk->nr_clus) { + err = 0; + if ((wb_i != chunk->nr_clus) && (wb_i != chunk->nr_clus - 1)) + dfr_debug("submit_fullpage_bio() called on a page (nr_clus %d, wb_i %d)", + chunk->nr_clus, wb_i); + + BUG_ON(nr_new > chunk->nr_clus); + } else { + dfr_debug("nr_new %d, nr_clus %d", nr_new, chunk->nr_clus); + err = -EBUSY; + } + + /* Update chunk's state */ + if (!err) + chunk->stat |= DFR_CHUNK_STAT_WB; + + return err; +} + + +static void +__defrag_check_fat_old( + IN struct super_block *sb, + IN struct inode *inode, + IN struct defrag_chunk_info *chunk) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + unsigned int clus = 0; + int err = 0, idx = 0, max_idx = 0; + + /* Get start_clus */ + clus = SDFAT_I(inode)->fid.start_clu; + + /* Follow FAT-chain */ + #define num_clusters(val) ((val) ? (s32)((val - 1) >> fsi->cluster_size_bits) + 1 : 0) + max_idx = num_clusters(SDFAT_I(inode)->i_size_ondisk); + for (idx = 0; idx < max_idx; idx++) { + + FAT32_CHECK_CLUSTER(fsi, clus, err); + ERR_HANDLE(err); + err = fat_ent_get(sb, clus, &clus); + ERR_HANDLE(err); + + if ((idx < max_idx - 1) && (IS_CLUS_EOF(clus) || IS_CLUS_FREE(clus))) { + dfr_err("FAT: inode %p, max_idx %d, idx %d, clus %08x, " + "f_clus %d, nr_clus %d", inode, max_idx, + idx, clus, chunk->f_clus, chunk->nr_clus); + BUG_ON(idx < max_idx - 1); + goto error; + } + } + +error: + return; +} + + +static void +__defrag_check_fat_new( + IN struct super_block *sb, + IN struct inode *inode, + IN struct defrag_chunk_info *chunk) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + unsigned int clus = 0; + int i = 0, err = 0; + + /* Check start of FAT-chain */ + if (chunk->prev_clus) { + FAT32_CHECK_CLUSTER(fsi, chunk->prev_clus, err); + BUG_ON(err); + err = fat_ent_get(sb, chunk->prev_clus, &clus); + BUG_ON(err); + } else { + clus = SDFAT_I(inode)->fid.start_clu; + } + if (sbi->dfr_new_clus[chunk->new_idx] != clus) { + dfr_err("FAT: inode %p, start_clus %08x, read_clus %08x", + inode, sbi->dfr_new_clus[chunk->new_idx], clus); + err = EIO; + goto error; + } + + /* Check inside of FAT-chain */ + if (chunk->nr_clus > 1) { + for (i = 0; i < chunk->nr_clus - 1; i++) { + FAT32_CHECK_CLUSTER(fsi, sbi->dfr_new_clus[chunk->new_idx + i], err); + BUG_ON(err); + err = fat_ent_get(sb, sbi->dfr_new_clus[chunk->new_idx + i], &clus); + BUG_ON(err); + if (sbi->dfr_new_clus[chunk->new_idx + i + 1] != clus) { + dfr_err("FAT: inode %p, new_clus %08x, read_clus %08x", + inode, sbi->dfr_new_clus[chunk->new_idx], clus); + err = EIO; + goto error; + } + } + clus = 0; + } + + /* Check end of FAT-chain */ + FAT32_CHECK_CLUSTER(fsi, sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1], err); + BUG_ON(err); + err = fat_ent_get(sb, sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1], &clus); + BUG_ON(err); + if ((chunk->next_clus & 0x0FFFFFFF) != (clus & 0x0FFFFFFF)) { + dfr_err("FAT: inode %p, next_clus %08x, read_clus %08x", inode, chunk->next_clus, clus); + err = EIO; + } + +error: + BUG_ON(err); +} + + +/** + * @fn __defrag_update_dirent + * @brief update DIR entry for defrag req + * @return void + * @param sb super block + * @param chunk given chunk + */ +static void +__defrag_update_dirent( + struct super_block *sb, + struct defrag_chunk_info *chunk) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &SDFAT_SB(sb)->fsi; + CHAIN_T dir; + DOS_DENTRY_T *dos_ep; + unsigned int entry = 0; + unsigned long long sector = 0; + unsigned short hi = 0, lo = 0; + int err = 0; + + dir.dir = GET64_HI(chunk->i_pos); + dir.flags = 0x1; // Assume non-continuous + + entry = GET64_LO(chunk->i_pos); + + FAT32_CHECK_CLUSTER(fsi, dir.dir, err); + BUG_ON(err); + dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, &dir, entry, §or); + + hi = GET32_HI(sbi->dfr_new_clus[chunk->new_idx]); + lo = GET32_LO(sbi->dfr_new_clus[chunk->new_idx]); + + dos_ep->start_clu_hi = cpu_to_le16(hi); + dos_ep->start_clu_lo = cpu_to_le16(lo); + + dcache_modify(sb, sector); +} + + +/** + * @fn defrag_update_fat_prev + * @brief update FAT chain for defrag requests + * @return void + * @param sb super block + * @param force flag to force FAT update + * @remark protected by super_block and volume lock + */ +void +defrag_update_fat_prev( + struct super_block *sb, + int force) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + struct defrag_info *sb_dfr = &sbi->dfr_info, *ino_dfr = NULL; + int skip = 0, done = 0; + + /* Check if FS_ERROR occurred */ + if (sb->s_flags & MS_RDONLY) { + dfr_err("RDONLY partition (err %d)", -EPERM); + goto out; + } + + list_for_each_entry(ino_dfr, &sb_dfr->entry, entry) { + struct inode *inode = &(container_of(ino_dfr, struct sdfat_inode_info, dfr_info)->vfs_inode); + struct sdfat_inode_info *ino_info = SDFAT_I(inode); + struct defrag_chunk_info *chunk_prev = NULL; + int i = 0, j = 0; + + mutex_lock(&ino_dfr->lock); + BUG_ON(atomic_read(&ino_dfr->stat) != DFR_INO_STAT_REQ); + for (i = 0; i < ino_dfr->nr_chunks; i++) { + struct defrag_chunk_info *chunk = NULL; + int err = 0; + + chunk = &(ino_dfr->chunks[i]); + BUG_ON(!chunk); + + /* Do nothing for already passed chunk */ + if (chunk->stat == DFR_CHUNK_STAT_PASS) { + done++; + continue; + } + + /* Handle error case */ + if (chunk->stat == DFR_CHUNK_STAT_ERR) { + err = -EINVAL; + goto error; + } + + /* Double-check clusters */ + if (chunk_prev && + (chunk->f_clus == chunk_prev->f_clus + chunk_prev->nr_clus) && + (chunk_prev->stat == DFR_CHUNK_STAT_PASS)) { + + err = defrag_validate_cluster(inode, chunk, 1); + + /* Handle continuous chunks in a file */ + if (!err) { + chunk->prev_clus = + sbi->dfr_new_clus[chunk_prev->new_idx + chunk_prev->nr_clus - 1]; + dfr_debug("prev->f_clus %d, prev->nr_clus %d, chunk->f_clus %d", + chunk_prev->f_clus, chunk_prev->nr_clus, chunk->f_clus); + } + } else { + err = defrag_validate_cluster(inode, chunk, 0); + } + + if (err) { + dfr_err("Cluster validation: inode %p, chunk->f_clus %d, err %d", + inode, chunk->f_clus, err); + goto error; + } + + /** + * Skip update_fat_prev if WB or update_fat_next not completed. + * Go to error case if FORCE set. + */ + if (__defrag_check_wb(sbi, chunk) || (chunk->stat != DFR_CHUNK_STAT_PREP)) { + if (force) { + err = -EPERM; + dfr_err("Skip case: inode %p, stat %x, f_clus %d, err %d", + inode, chunk->stat, chunk->f_clus, err); + goto error; + } + skip++; + continue; + } + +#ifdef CONFIG_SDFAT_DFR_DEBUG + /* SPO test */ + defrag_spo_test(sb, DFR_SPO_RANDOM, __func__); +#endif + + /* Update chunk's previous cluster */ + if (chunk->prev_clus == 0) { + /* For the first cluster of a file */ + /* Update ino_info->fid.start_clu */ + ino_info->fid.start_clu = sbi->dfr_new_clus[chunk->new_idx]; + __defrag_update_dirent(sb, chunk); + } else { + FAT32_CHECK_CLUSTER(fsi, chunk->prev_clus, err); + BUG_ON(err); + if (fat_ent_set(sb, + chunk->prev_clus, + sbi->dfr_new_clus[chunk->new_idx])) { + err = -EIO; + goto error; + } + } + + /* Clear extent cache */ + extent_cache_inval_inode(inode); + + /* Update FID info */ + ino_info->fid.hint_bmap.off = CLUS_EOF; + ino_info->fid.hint_bmap.clu = 0; + + /* Clear old FAT-chain */ + for (j = 0; j < chunk->nr_clus; j++) + defrag_free_cluster(sb, chunk->d_clus + j); + + /* Mark this chunk PASS */ + chunk->stat = DFR_CHUNK_STAT_PASS; + __defrag_check_fat_new(sb, inode, chunk); + + done++; + +error: + if (err) { + /** + * chunk->new_idx != 0 means this chunk needs to be cleaned up + */ + if (chunk->new_idx) { + /* Free already allocated clusters */ + for (j = 0; j < chunk->nr_clus; j++) { + if (sbi->dfr_new_clus[chunk->new_idx + j]) { + defrag_free_cluster(sb, sbi->dfr_new_clus[chunk->new_idx + j]); + sbi->dfr_new_clus[chunk->new_idx + j] = 0; + } + } + + __defrag_check_fat_old(sb, inode, chunk); + } + + /** + * chunk->new_idx == 0 means this chunk already cleaned up + */ + chunk->new_idx = 0; + chunk->stat = DFR_CHUNK_STAT_ERR; + } + + chunk_prev = chunk; + } + BUG_ON(!mutex_is_locked(&ino_dfr->lock)); + mutex_unlock(&ino_dfr->lock); + } + +out: + if (skip) { + dfr_debug("%s skipped (nr_reqs %d, done %d, skip %d)", + __func__, sb_dfr->nr_chunks - 1, done, skip); + } else { + /* Make dfr_reserved_clus zero */ + if (sbi->dfr_reserved_clus > 0) { + if (fsi->reserved_clusters < sbi->dfr_reserved_clus) { + dfr_err("Reserved count: reserved_clus %d, dfr_reserved_clus %d", + fsi->reserved_clusters, sbi->dfr_reserved_clus); + BUG_ON(fsi->reserved_clusters < sbi->dfr_reserved_clus); + } + + defrag_reserve_clusters(sb, 0 - sbi->dfr_reserved_clus); + } + + dfr_debug("%s done (nr_reqs %d, done %d)", __func__, sb_dfr->nr_chunks - 1, done); + } +} + + +/** + * @fn defrag_update_fat_next + * @brief update FAT chain for defrag requests + * @return void + * @param sb super block + * @remark protected by super_block and volume lock + */ +void +defrag_update_fat_next( + struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + struct defrag_info *sb_dfr = &sbi->dfr_info, *ino_dfr = NULL; + struct defrag_chunk_info *chunk = NULL; + int done = 0, i = 0, j = 0, err = 0; + + /* Check if FS_ERROR occurred */ + if (sb->s_flags & MS_RDONLY) { + dfr_err("RDONLY partition (err %d)", -EROFS); + goto out; + } + + list_for_each_entry(ino_dfr, &sb_dfr->entry, entry) { + + for (i = 0; i < ino_dfr->nr_chunks; i++) { + int skip = 0; + + chunk = &(ino_dfr->chunks[i]); + + /* Do nothing if error occurred or update_fat_next already passed */ + if (chunk->stat == DFR_CHUNK_STAT_ERR) + continue; + if (chunk->stat & DFR_CHUNK_STAT_FAT) { + done++; + continue; + } + + /* Ship this chunk if get_block not passed for this chunk */ + for (j = 0; j < chunk->nr_clus; j++) { + if (sbi->dfr_new_clus[chunk->new_idx + j] == 0) { + skip = 1; + break; + } + } + if (skip) + continue; + + /* Update chunk's next cluster */ + FAT32_CHECK_CLUSTER(fsi, + sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1], err); + BUG_ON(err); + if (fat_ent_set(sb, + sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1], + chunk->next_clus)) + goto out; + +#ifdef CONFIG_SDFAT_DFR_DEBUG + /* SPO test */ + defrag_spo_test(sb, DFR_SPO_RANDOM, __func__); +#endif + + /* Update chunk's state */ + chunk->stat |= DFR_CHUNK_STAT_FAT; + done++; + } + } + +out: + dfr_debug("%s done (nr_reqs %d, done %d)", __func__, sb_dfr->nr_chunks - 1, done); +} + + +/** + * @fn defrag_check_discard + * @brief check if we can send discard for this AU, if so, send discard + * @return void + * @param sb super block + * @remark protected by super_block and volume lock + */ +void +defrag_check_discard( + IN struct super_block *sb) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + AMAP_T *amap = SDFAT_SB(sb)->fsi.amap; + AU_INFO_T *au = NULL; + struct defrag_info *sb_dfr = &(SDFAT_SB(sb)->dfr_info); + unsigned int tmp[DFR_MAX_AU_MOVED]; + int i = 0, j = 0; + + BUG_ON(!amap); + + if (!(SDFAT_SB(sb)->options.discard) || + !(SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART)) + return; + + memset(tmp, 0, sizeof(int) * DFR_MAX_AU_MOVED); + + for (i = REQ_HEADER_IDX + 1; i < sb_dfr->nr_chunks; i++) { + struct defrag_chunk_info *chunk = &(sb_dfr->chunks[i]); + int skip = 0; + + au = GET_AU(amap, i_AU_of_CLU(amap, chunk->d_clus)); + + /* Send DISCARD for free AU */ + if ((IS_AU_IGNORED(au, amap)) && + (amap_get_freeclus(sb, chunk->d_clus) == CLUS_PER_AU(sb))) { + sector_t blk = 0, nr_blks = 0; + unsigned int au_align_factor = amap->option.au_align_factor % amap->option.au_size; + + BUG_ON(au->idx == 0); + + /* Avoid multiple DISCARD */ + for (j = 0; j < DFR_MAX_AU_MOVED; j++) { + if (tmp[j] == au->idx) { + skip = 1; + break; + } + } + if (skip == 1) + continue; + + /* Send DISCARD cmd */ + blk = (sector_t) (((au->idx * CLUS_PER_AU(sb)) << fsi->sect_per_clus_bits) + - au_align_factor); + nr_blks = ((sector_t)CLUS_PER_AU(sb)) << fsi->sect_per_clus_bits; + + dfr_debug("Send DISCARD for AU[%d] (blk %08zx)", au->idx, blk); + sb_issue_discard(sb, blk, nr_blks, GFP_NOFS, 0); + + /* Save previous AU's index */ + for (j = 0; j < DFR_MAX_AU_MOVED; j++) { + if (!tmp[j]) { + tmp[j] = au->idx; + break; + } + } + } + } +} + + +/** + * @fn defrag_free_cluster + * @brief free uneccessary cluster + * @return void + * @param sb super block + * @param clus physical cluster num + * @remark protected by super_block and volume lock + */ +int +defrag_free_cluster( + struct super_block *sb, + unsigned int clus) +{ + FS_INFO_T *fsi = &SDFAT_SB(sb)->fsi; + unsigned int val = 0; + s32 err = 0; + + FAT32_CHECK_CLUSTER(fsi, clus, err); + BUG_ON(err); + if (fat_ent_get(sb, clus, &val)) + return -EIO; + if (val) { + if (fat_ent_set(sb, clus, 0)) + return -EIO; + } else { + dfr_err("Free: Already freed, clus %08x, val %08x", clus, val); + BUG_ON(!val); + } + + set_sb_dirty(sb); + fsi->used_clusters--; + if (fsi->amap) + amap_release_cluster(sb, clus); + + return 0; +} + + +/** + * @fn defrag_check_defrag_required + * @brief check if defrag required + * @return 1 if required, 0 otherwise + * @param sb super block + * @param totalau # of total AUs + * @param cleanau # of clean AUs + * @param fullau # of full AUs + * @remark protected by super_block + */ +int +defrag_check_defrag_required( + IN struct super_block *sb, + OUT int *totalau, + OUT int *cleanau, + OUT int *fullau) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + AMAP_T *amap = NULL; + int clean_ratio = 0, frag_ratio = 0; + int ret = 0; + + if (!sb || !(SDFAT_SB(sb)->options.defrag)) + return 0; + + /* Check DFR_DEFAULT_STOP_RATIO first */ + fsi = &(SDFAT_SB(sb)->fsi); + if (fsi->used_clusters == (unsigned int)(~0)) { + if (fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters)) + return -EIO; + } + if (fsi->used_clusters * DFR_FULL_RATIO >= fsi->num_clusters * DFR_DEFAULT_STOP_RATIO) { + dfr_debug("used_clusters %d, num_clusters %d", fsi->used_clusters, fsi->num_clusters); + return 0; + } + + /* Check clean/frag ratio */ + amap = SDFAT_SB(sb)->fsi.amap; + BUG_ON(!amap); + + clean_ratio = (amap->n_clean_au * 100) / amap->n_au; + if (amap->n_full_au) + frag_ratio = ((amap->n_au - amap->n_clean_au) * 100) / amap->n_full_au; + else + frag_ratio = ((amap->n_au - amap->n_clean_au) * 100) / + (fsi->used_clusters * CLUS_PER_AU(sb)); + + /* + * Wake-up defrag_daemon: + * when # of clean AUs too small, or frag_ratio exceeds the limit + */ + if ((clean_ratio < DFR_DEFAULT_WAKEUP_RATIO) || + ((clean_ratio < DFR_DEFAULT_CLEAN_RATIO) && (frag_ratio >= DFR_DEFAULT_FRAG_RATIO))) { + + if (totalau) + *totalau = amap->n_au; + if (cleanau) + *cleanau = amap->n_clean_au; + if (fullau) + *fullau = amap->n_full_au; + ret = 1; + } + + return ret; +} + + +/** + * @fn defrag_check_defrag_required + * @brief check defrag status on inode + * @return 1 if defrag in on, 0 otherwise + * @param inode inode + * @param start logical start addr + * @param end logical end addr + * @param cancel flag to cancel defrag + * @param caller caller info + */ +int +defrag_check_defrag_on( + INOUT struct inode *inode, + IN loff_t start, + IN loff_t end, + IN int cancel, + IN const char *caller) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + struct defrag_info *ino_dfr = &(SDFAT_I(inode)->dfr_info); + unsigned int clus_start = 0, clus_end = 0; + int ret = 0, i = 0; + + if (!inode || (start == end)) + return 0; + + mutex_lock(&ino_dfr->lock); + /* Check if this inode is on defrag */ + if (atomic_read(&ino_dfr->stat) == DFR_INO_STAT_REQ) { + + clus_start = start >> (fsi->cluster_size_bits); + clus_end = (end >> (fsi->cluster_size_bits)) + + ((end & (fsi->cluster_size - 1)) ? 1 : 0); + + if (!ino_dfr->chunks) + goto error; + + /* Check each chunk in given inode */ + for (i = 0; i < ino_dfr->nr_chunks; i++) { + struct defrag_chunk_info *chunk = &(ino_dfr->chunks[i]); + unsigned int chunk_start = 0, chunk_end = 0; + + /* Skip this chunk when error occurred or it already passed defrag process */ + if ((chunk->stat == DFR_CHUNK_STAT_ERR) || (chunk->stat == DFR_CHUNK_STAT_PASS)) + continue; + + chunk_start = chunk->f_clus; + chunk_end = chunk->f_clus + chunk->nr_clus; + + if (((clus_start >= chunk_start) && (clus_start < chunk_end)) || + ((clus_end > chunk_start) && (clus_end <= chunk_end)) || + ((clus_start < chunk_start) && (clus_end > chunk_end))) { + ret = 1; + if (cancel) { + chunk->stat = DFR_CHUNK_STAT_ERR; + dfr_debug("Defrag canceled: inode %p, start %08x, end %08x, caller %s", + inode, clus_start, clus_end, caller); + } + } + } + } + +error: + BUG_ON(!mutex_is_locked(&ino_dfr->lock)); + mutex_unlock(&ino_dfr->lock); + return ret; +} + + +#ifdef CONFIG_SDFAT_DFR_DEBUG +/** + * @fn defrag_spo_test + * @brief test SPO while defrag running + * @return void + * @param sb super block + * @param flag SPO debug flag + * @param caller caller info + */ +void +defrag_spo_test( + struct super_block *sb, + int flag, + const char *caller) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + if (!sb || !(SDFAT_SB(sb)->options.defrag)) + return; + + if (flag == sbi->dfr_spo_flag) { + dfr_err("Defrag SPO test (flag %d, caller %s)", flag, caller); + panic("Defrag SPO test"); + } +} +#endif /* CONFIG_SDFAT_DFR_DEBUG */ + + +#endif /* CONFIG_SDFAT_DFR */ diff --git a/fs/sdfat/dfr.h b/fs/sdfat/dfr.h new file mode 100644 index 0000000000000..da98605020d34 --- /dev/null +++ b/fs/sdfat/dfr.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _SDFAT_DEFRAG_H +#define _SDFAT_DEFRAG_H + +#ifdef CONFIG_SDFAT_DFR + +/* Tuning parameters */ +#define DFR_MIN_TIMEOUT (1 * HZ) // Minimum timeout for forced-sync +#define DFR_DEFAULT_TIMEOUT (10 * HZ) // Default timeout for forced-sync + +#define DFR_DEFAULT_CLEAN_RATIO (50) // Wake-up daemon when clean AU ratio under 50% +#define DFR_DEFAULT_WAKEUP_RATIO (10) // Wake-up daemon when clean AU ratio under 10%, regardless of frag_ratio + +#define DFR_DEFAULT_FRAG_RATIO (130) // Wake-up daemon when frag_ratio over 130% + +#define DFR_DEFAULT_PACKING_RATIO (10) // Call allocator with PACKING flag, when clean AU ratio under 10% + +#define DFR_DEFAULT_STOP_RATIO (98) // Stop defrag_daemon when disk used ratio over 98% +#define DFR_FULL_RATIO (100) + +#define DFR_MAX_AU_MOVED (16) // Maximum # of AUs for a request + + +/* Debugging support*/ +#define dfr_err(fmt, args...) pr_err("DFR: " fmt "\n", args) + +#ifdef CONFIG_SDFAT_DFR_DEBUG +#define dfr_debug(fmt, args...) pr_debug("DFR: " fmt "\n", args) +#else +#define dfr_debug(fmt, args...) +#endif + + +/* Error handling */ +#define ERR_HANDLE(err) { \ + if (err) { \ + dfr_debug("err %d", err); \ + goto error; \ + } \ +} + +#define ERR_HANDLE2(cond, err, val) { \ + if (cond) { \ + err = val; \ + dfr_debug("err %d", err); \ + goto error; \ + } \ +} + + +/* Arguments IN-OUT */ +#define IN +#define OUT +#define INOUT + + +/* Macros */ +#define GET64_HI(var64) ((unsigned int)((var64) >> 32)) +#define GET64_LO(var64) ((unsigned int)(((var64) << 32) >> 32)) +#define SET64_HI(dst64, var32) { (dst64) = ((loff_t)(var32) << 32) | ((dst64) & 0x00000000ffffffffLL); } +#define SET64_LO(dst64, var32) { (dst64) = ((dst64) & 0xffffffff00000000LL) | ((var32) & 0x00000000ffffffffLL); } + +#define GET32_HI(var32) ((unsigned short)((var32) >> 16)) +#define GET32_LO(var32) ((unsigned short)(((var32) << 16) >> 16)) +#define SET32_HI(dst32, var16) { (dst32) = ((unsigned int)(var16) << 16) | ((dst32) & 0x0000ffff); } +#define SET32_LO(dst32, var16) { (dst32) = ((dst32) & 0xffff0000) | ((unsigned int)(var16) & 0x0000ffff); } + + +/* FAT32 related */ +#define FAT32_EOF (0x0fffffff) +#define FAT32_RESERVED (0x0ffffff7) +#define FAT32_UNUSED_CLUS (2) + +#define CLUS_PER_AU(sb) ( \ + (SDFAT_SB(sb)->options.amap_opt.sect_per_au) >> (SDFAT_SB(sb)->fsi.sect_per_clus_bits) \ +) +#define PAGES_PER_AU(sb) ( \ + ((SDFAT_SB(sb)->options.amap_opt.sect_per_au) << ((sb)->s_blocksize_bits)) \ + >> PAGE_SHIFT \ +) +#define PAGES_PER_CLUS(sb) ((SDFAT_SB(sb)->fsi.cluster_size) >> PAGE_SHIFT) + +#define FAT32_CHECK_CLUSTER(fsi, clus, err) \ + { \ + if (((clus) < FAT32_UNUSED_CLUS) || \ + ((clus) > (fsi)->num_clusters) || \ + ((clus) >= FAT32_RESERVED)) { \ + dfr_err("clus %08x, fsi->num_clusters %08x", (clus), (fsi)->num_clusters); \ + err = -EINVAL; \ + } else { \ + err = 0; \ + } \ + } + + +/* IOCTL_DFR_INFO */ +struct defrag_info_arg { + /* PBS info */ + unsigned int sec_sz; + unsigned int clus_sz; + unsigned long long total_sec; + unsigned long long fat_offset_sec; + unsigned int fat_sz_sec; + unsigned int n_fat; + unsigned int hidden_sectors; + + /* AU info */ + unsigned int sec_per_au; +}; + + +/* IOC_DFR_TRAV */ +#define DFR_TRAV_HEADER_IDX (0) + +#define DFR_TRAV_TYPE_HEADER (0x0000000F) +#define DFR_TRAV_TYPE_DIR (1) +#define DFR_TRAV_TYPE_FILE (2) +#define DFR_TRAV_TYPE_TEST (DFR_TRAV_TYPE_HEADER | 0x10000000) + +#define DFR_TRAV_ROOT_IPOS (0xFFFFFFFFFFFFFFFFLL) + +struct defrag_trav_arg { + int type; + unsigned int start_clus; + loff_t i_pos; + char name[MAX_DOSNAME_BUF_SIZE]; + char dummy1; + int dummy2; +}; + +#define DFR_TRAV_STAT_DONE (0x1) +#define DFR_TRAV_STAT_MORE (0x2) +#define DFR_TRAV_STAT_ERR (0xFF) + +struct defrag_trav_header { + int type; + unsigned int start_clus; + loff_t i_pos; + char name[MAX_DOSNAME_BUF_SIZE]; + char stat; + unsigned int nr_entries; +}; + + +/* IOC_DFR_REQ */ +#define REQ_HEADER_IDX (0) + +#define DFR_CHUNK_STAT_ERR (0xFFFFFFFF) +#define DFR_CHUNK_STAT_REQ (0x1) +#define DFR_CHUNK_STAT_WB (0x2) +#define DFR_CHUNK_STAT_FAT (0x4) +#define DFR_CHUNK_STAT_PREP (DFR_CHUNK_STAT_REQ | DFR_CHUNK_STAT_WB | DFR_CHUNK_STAT_FAT) +#define DFR_CHUNK_STAT_PASS (0x0000000F) + +struct defrag_chunk_header { + int mode; + unsigned int nr_chunks; + loff_t dummy1; + int dummy2[4]; + union { + int *dummy3; + int dummy4; + }; + int dummy5; +}; + +struct defrag_chunk_info { + int stat; + /* File related */ + unsigned int f_clus; + loff_t i_pos; + /* Cluster related */ + unsigned int d_clus; + unsigned int nr_clus; + unsigned int prev_clus; + unsigned int next_clus; + union { + void *dummy; + /* req status */ + unsigned int new_idx; + }; + /* AU related */ + unsigned int au_clus; +}; + + +/* Global info */ +#define DFR_MODE_BACKGROUND (0x1) +#define DFR_MODE_FOREGROUND (0x2) +#define DFR_MODE_ONESHOT (0x4) +#define DFR_MODE_BATCHED (0x8) +#define DFR_MODE_TEST (DFR_MODE_BACKGROUND | 0x10000000) + +#define DFR_SB_STAT_IDLE (0) +#define DFR_SB_STAT_REQ (1) +#define DFR_SB_STAT_VALID (2) + +#define DFR_INO_STAT_IDLE (0) +#define DFR_INO_STAT_REQ (1) +struct defrag_info { + struct mutex lock; + atomic_t stat; + struct defrag_chunk_info *chunks; + unsigned int nr_chunks; + struct list_head entry; +}; + + +/* SPO test flags */ +#define DFR_SPO_NONE (0) +#define DFR_SPO_NORMAL (1) +#define DFR_SPO_DISCARD (2) +#define DFR_SPO_FAT_NEXT (3) +#define DFR_SPO_RANDOM (4) + + +/* Extern functions */ +int defrag_get_info(struct super_block *sb, struct defrag_info_arg *arg); + +int defrag_scan_dir(struct super_block *sb, struct defrag_trav_arg *arg); + +int defrag_validate_cluster(struct inode *inode, struct defrag_chunk_info *chunk, int skip_prev); +int defrag_reserve_clusters(struct super_block *sb, int nr_clus); +int defrag_mark_ignore(struct super_block *sb, unsigned int clus); +void defrag_unmark_ignore_all(struct super_block *sb); + +int defrag_map_cluster(struct inode *inode, unsigned int clu_offset, unsigned int *clu); +void defrag_writepage_end_io(struct page *page); + +void defrag_update_fat_prev(struct super_block *sb, int force); +void defrag_update_fat_next(struct super_block *sb); +void defrag_check_discard(struct super_block *sb); +int defrag_free_cluster(struct super_block *sb, unsigned int clus); + +int defrag_check_defrag_required(struct super_block *sb, int *totalau, int *cleanau, int *fullau); +int defrag_check_defrag_on(struct inode *inode, loff_t start, loff_t end, int cancel, const char *caller); + +#ifdef CONFIG_SDFAT_DFR_DEBUG +void defrag_spo_test(struct super_block *sb, int flag, const char *caller); +#endif + +#endif /* CONFIG_SDFAT_DFR */ + +#endif /* _SDFAT_DEFRAG_H */ + diff --git a/fs/sdfat/extent.c b/fs/sdfat/extent.c new file mode 100644 index 0000000000000..59e096530ddad --- /dev/null +++ b/fs/sdfat/extent.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* + * linux/fs/fat/cache.c + * + * Written 1992,1993 by Werner Almesberger + * + * Mar 1999. AV. Changed cache, so that it uses the starting cluster instead + * of inode number. + * May 1999. AV. Fixed the bogosity with FAT32 (read "FAT28"). Fscking lusers. + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : extent.c */ +/* PURPOSE : Improve the performance of traversing fat chain. */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include "sdfat.h" +#include "core.h" + +#define EXTENT_CACHE_VALID 0 +/* this must be > 0. */ +#define EXTENT_MAX_CACHE 16 + +struct extent_cache { + struct list_head cache_list; + u32 nr_contig; /* number of contiguous clusters */ + u32 fcluster; /* cluster number in the file. */ + u32 dcluster; /* cluster number on disk. */ +}; + +struct extent_cache_id { + u32 id; + u32 nr_contig; + u32 fcluster; + u32 dcluster; +}; + +static struct kmem_cache *extent_cache_cachep; + +static void init_once(void *c) +{ + struct extent_cache *cache = (struct extent_cache *)c; + + INIT_LIST_HEAD(&cache->cache_list); +} + +s32 extent_cache_init(void) +{ + extent_cache_cachep = kmem_cache_create("sdfat_extent_cache", + sizeof(struct extent_cache), + 0, SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, + init_once); + if (!extent_cache_cachep) + return -ENOMEM; + return 0; +} + +void extent_cache_shutdown(void) +{ + if (!extent_cache_cachep) + return; + kmem_cache_destroy(extent_cache_cachep); +} + +void extent_cache_init_inode(struct inode *inode) +{ + EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); + + spin_lock_init(&extent->cache_lru_lock); + extent->nr_caches = 0; + extent->cache_valid_id = EXTENT_CACHE_VALID + 1; + INIT_LIST_HEAD(&extent->cache_lru); +} + +static inline struct extent_cache *extent_cache_alloc(void) +{ + return kmem_cache_alloc(extent_cache_cachep, GFP_NOFS); +} + +static inline void extent_cache_free(struct extent_cache *cache) +{ + BUG_ON(!list_empty(&cache->cache_list)); + kmem_cache_free(extent_cache_cachep, cache); +} + +static inline void extent_cache_update_lru(struct inode *inode, + struct extent_cache *cache) +{ + EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); + + if (extent->cache_lru.next != &cache->cache_list) + list_move(&cache->cache_list, &extent->cache_lru); +} + +static u32 extent_cache_lookup(struct inode *inode, u32 fclus, + struct extent_cache_id *cid, + u32 *cached_fclus, u32 *cached_dclus) +{ + EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); + + static struct extent_cache nohit = { .fcluster = 0, }; + + struct extent_cache *hit = &nohit, *p; + u32 offset = CLUS_EOF; + + spin_lock(&extent->cache_lru_lock); + list_for_each_entry(p, &extent->cache_lru, cache_list) { + /* Find the cache of "fclus" or nearest cache. */ + if (p->fcluster <= fclus && hit->fcluster < p->fcluster) { + hit = p; + if ((hit->fcluster + hit->nr_contig) < fclus) { + offset = hit->nr_contig; + } else { + offset = fclus - hit->fcluster; + break; + } + } + } + if (hit != &nohit) { + extent_cache_update_lru(inode, hit); + + cid->id = extent->cache_valid_id; + cid->nr_contig = hit->nr_contig; + cid->fcluster = hit->fcluster; + cid->dcluster = hit->dcluster; + *cached_fclus = cid->fcluster + offset; + *cached_dclus = cid->dcluster + offset; + } + spin_unlock(&extent->cache_lru_lock); + + return offset; +} + +static struct extent_cache *extent_cache_merge(struct inode *inode, + struct extent_cache_id *new) +{ + EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); + + struct extent_cache *p; + + list_for_each_entry(p, &extent->cache_lru, cache_list) { + /* Find the same part as "new" in cluster-chain. */ + if (p->fcluster == new->fcluster) { + ASSERT(p->dcluster == new->dcluster); + if (new->nr_contig > p->nr_contig) + p->nr_contig = new->nr_contig; + return p; + } + } + return NULL; +} + +static void extent_cache_add(struct inode *inode, struct extent_cache_id *new) +{ + EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); + + struct extent_cache *cache, *tmp; + + if (new->fcluster == -1) /* dummy cache */ + return; + + spin_lock(&extent->cache_lru_lock); + if (new->id != EXTENT_CACHE_VALID && + new->id != extent->cache_valid_id) + goto out; /* this cache was invalidated */ + + cache = extent_cache_merge(inode, new); + if (cache == NULL) { + if (extent->nr_caches < EXTENT_MAX_CACHE) { + extent->nr_caches++; + spin_unlock(&extent->cache_lru_lock); + + tmp = extent_cache_alloc(); + if (!tmp) { + spin_lock(&extent->cache_lru_lock); + extent->nr_caches--; + spin_unlock(&extent->cache_lru_lock); + return; + } + + spin_lock(&extent->cache_lru_lock); + cache = extent_cache_merge(inode, new); + if (cache != NULL) { + extent->nr_caches--; + extent_cache_free(tmp); + goto out_update_lru; + } + cache = tmp; + } else { + struct list_head *p = extent->cache_lru.prev; + cache = list_entry(p, struct extent_cache, cache_list); + } + cache->fcluster = new->fcluster; + cache->dcluster = new->dcluster; + cache->nr_contig = new->nr_contig; + } +out_update_lru: + extent_cache_update_lru(inode, cache); +out: + spin_unlock(&extent->cache_lru_lock); +} + +/* + * Cache invalidation occurs rarely, thus the LRU chain is not updated. It + * fixes itself after a while. + */ +static void __extent_cache_inval_inode(struct inode *inode) +{ + EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); + struct extent_cache *cache; + + while (!list_empty(&extent->cache_lru)) { + cache = list_entry(extent->cache_lru.next, + struct extent_cache, cache_list); + list_del_init(&cache->cache_list); + extent->nr_caches--; + extent_cache_free(cache); + } + /* Update. The copy of caches before this id is discarded. */ + extent->cache_valid_id++; + if (extent->cache_valid_id == EXTENT_CACHE_VALID) + extent->cache_valid_id++; +} + +void extent_cache_inval_inode(struct inode *inode) +{ + EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); + + spin_lock(&extent->cache_lru_lock); + __extent_cache_inval_inode(inode); + spin_unlock(&extent->cache_lru_lock); +} + +static inline s32 cache_contiguous(struct extent_cache_id *cid, u32 dclus) +{ + cid->nr_contig++; + return ((cid->dcluster + cid->nr_contig) == dclus); +} + +static inline void cache_init(struct extent_cache_id *cid, u32 fclus, u32 dclus) +{ + cid->id = EXTENT_CACHE_VALID; + cid->fcluster = fclus; + cid->dcluster = dclus; + cid->nr_contig = 0; +} + +s32 extent_get_clus(struct inode *inode, u32 cluster, u32 *fclus, + u32 *dclus, u32 *last_dclus, s32 allow_eof) +{ + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + u32 limit = fsi->num_clusters; + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + struct extent_cache_id cid; + u32 content; + + /* FOR GRACEFUL ERROR HANDLING */ + if (IS_CLUS_FREE(fid->start_clu)) { + sdfat_fs_error(sb, "invalid access to " + "extent cache (entry 0x%08x)", fid->start_clu); + ASSERT(0); + return -EIO; + } + + *fclus = 0; + *dclus = fid->start_clu; + *last_dclus = *dclus; + + /* + * Don`t use extent_cache if zero offset or non-cluster allocation + */ + if ((cluster == 0) || IS_CLUS_EOF(*dclus)) + return 0; + + cache_init(&cid, CLUS_EOF, CLUS_EOF); + + if (extent_cache_lookup(inode, cluster, &cid, fclus, dclus) == CLUS_EOF) { + /* + * dummy, always not contiguous + * This is reinitialized by cache_init(), later. + */ + ASSERT((cid.id == EXTENT_CACHE_VALID) + && (cid.fcluster == CLUS_EOF) + && (cid.dcluster == CLUS_EOF) + && (cid.nr_contig == 0)); + } + + if (*fclus == cluster) + return 0; + + while (*fclus < cluster) { + /* prevent the infinite loop of cluster chain */ + if (*fclus > limit) { + sdfat_fs_error(sb, + "%s: detected the cluster chain loop" + " (i_pos %u)", __func__, + (*fclus)); + return -EIO; + } + + if (fat_ent_get_safe(sb, *dclus, &content)) + return -EIO; + + *last_dclus = *dclus; + *dclus = content; + (*fclus)++; + + if (IS_CLUS_EOF(content)) { + if (!allow_eof) { + sdfat_fs_error(sb, + "%s: invalid cluster chain (i_pos %u," + "last_clus 0x%08x is EOF)", + __func__, *fclus, (*last_dclus)); + return -EIO; + } + + break; + } + + if (!cache_contiguous(&cid, *dclus)) + cache_init(&cid, *fclus, *dclus); + } + + extent_cache_add(inode, &cid); + return 0; +} diff --git a/fs/sdfat/fatent.c b/fs/sdfat/fatent.c new file mode 100644 index 0000000000000..fca32a50d3363 --- /dev/null +++ b/fs/sdfat/fatent.c @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : fatent.c */ +/* PURPOSE : sdFAT FAT entry manager */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include + +#include "sdfat.h" +#include "core.h" + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ +/* All buffer structures are protected w/ fsi->v_sem */ + +/*----------------------------------------------------------------------*/ +/* Static functions */ +/*----------------------------------------------------------------------*/ + +/*======================================================================*/ +/* FAT Read/Write Functions */ +/*======================================================================*/ +/* in : sb, loc + * out: content + * returns 0 on success, -1 on error + */ +static s32 exfat_ent_get(struct super_block *sb, u32 loc, u32 *content) +{ + u32 off, _content; + u64 sec; + u8 *fat_sector; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + /* fsi->vol_type == EXFAT */ + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-2)); + off = (loc << 2) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + _content = le32_to_cpu(*(__le32 *)(&fat_sector[off])); + + /* remap reserved clusters to simplify code */ + if (_content >= CLUSTER_32(0xFFFFFFF8)) + _content = CLUS_EOF; + + *content = CLUSTER_32(_content); + return 0; +} + +static s32 exfat_ent_set(struct super_block *sb, u32 loc, u32 content) +{ + u32 off; + u64 sec; + u8 *fat_sector; + __le32 *fat_entry; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-2)); + off = (loc << 2) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + fat_entry = (__le32 *)&(fat_sector[off]); + *fat_entry = cpu_to_le32(content); + + return fcache_modify(sb, sec); +} + +#define FATENT_FAT32_VALID_MASK (0x0FFFFFFFU) +#define FATENT_FAT32_IGNORE_MASK (0xF0000000U) +static s32 fat32_ent_get(struct super_block *sb, u32 loc, u32 *content) +{ + u32 off, _content; + u64 sec; + u8 *fat_sector; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-2)); + off = (loc << 2) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + _content = le32_to_cpu(*(__le32 *)(&fat_sector[off])); + _content &= FATENT_FAT32_VALID_MASK; + + /* remap reserved clusters to simplify code */ + if (_content == CLUSTER_32(0x0FFFFFF7U)) + _content = CLUS_BAD; + else if (_content >= CLUSTER_32(0x0FFFFFF8U)) + _content = CLUS_EOF; + + *content = CLUSTER_32(_content); + return 0; +} + +static s32 fat32_ent_set(struct super_block *sb, u32 loc, u32 content) +{ + u32 off; + u64 sec; + u8 *fat_sector; + __le32 *fat_entry; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + content &= FATENT_FAT32_VALID_MASK; + + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-2)); + off = (loc << 2) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + fat_entry = (__le32 *)&(fat_sector[off]); + content |= (le32_to_cpu(*fat_entry) & FATENT_FAT32_IGNORE_MASK); + *fat_entry = cpu_to_le32(content); + + return fcache_modify(sb, sec); +} + +#define FATENT_FAT16_VALID_MASK (0x0000FFFFU) +static s32 fat16_ent_get(struct super_block *sb, u32 loc, u32 *content) +{ + u32 off, _content; + u64 sec; + u8 *fat_sector; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-1)); + off = (loc << 1) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + _content = (u32)le16_to_cpu(*(__le16 *)(&fat_sector[off])); + _content &= FATENT_FAT16_VALID_MASK; + + /* remap reserved clusters to simplify code */ + if (_content == CLUSTER_16(0xFFF7U)) + _content = CLUS_BAD; + else if (_content >= CLUSTER_16(0xFFF8U)) + _content = CLUS_EOF; + + *content = CLUSTER_32(_content); + return 0; +} + +static s32 fat16_ent_set(struct super_block *sb, u32 loc, u32 content) +{ + u32 off; + u64 sec; + u8 *fat_sector; + __le16 *fat_entry; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + content &= FATENT_FAT16_VALID_MASK; + + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-1)); + off = (loc << 1) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + fat_entry = (__le16 *)&(fat_sector[off]); + *fat_entry = cpu_to_le16(content); + + return fcache_modify(sb, sec); +} + +#define FATENT_FAT12_VALID_MASK (0x00000FFFU) +static s32 fat12_ent_get(struct super_block *sb, u32 loc, u32 *content) +{ + u32 off, _content; + u64 sec; + u8 *fat_sector; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + sec = fsi->FAT1_start_sector + ((loc + (loc >> 1)) >> sb->s_blocksize_bits); + off = (loc + (loc >> 1)) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + if (off == (u32)(sb->s_blocksize - 1)) { + _content = (u32) fat_sector[off]; + + fat_sector = fcache_getblk(sb, ++sec); + if (!fat_sector) + return -EIO; + + _content |= (u32) fat_sector[0] << 8; + } else { + _content = get_unaligned_le16(&fat_sector[off]); + } + + if (loc & 1) + _content >>= 4; + + _content &= FATENT_FAT12_VALID_MASK; + + /* remap reserved clusters to simplify code */ + if (_content == CLUSTER_16(0x0FF7U)) + _content = CLUS_BAD; + else if (_content >= CLUSTER_16(0x0FF8U)) + _content = CLUS_EOF; + + *content = CLUSTER_32(_content); + return 0; +} + +static s32 fat12_ent_set(struct super_block *sb, u32 loc, u32 content) +{ + u32 off; + u64 sec; + u8 *fat_sector, *fat_entry; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + content &= FATENT_FAT12_VALID_MASK; + + sec = fsi->FAT1_start_sector + ((loc + (loc >> 1)) >> sb->s_blocksize_bits); + off = (loc + (loc >> 1)) & (u32)(sb->s_blocksize - 1); + + fat_sector = fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + if (loc & 1) { /* odd */ + + content <<= 4; + + if (off == (u32)(sb->s_blocksize-1)) { + fat_sector[off] = (u8)(content | (fat_sector[off] & 0x0F)); + if (fcache_modify(sb, sec)) + return -EIO; + + fat_sector = fcache_getblk(sb, ++sec); + if (!fat_sector) + return -EIO; + + fat_sector[0] = (u8)(content >> 8); + } else { + fat_entry = &(fat_sector[off]); + content |= 0x000F & get_unaligned_le16(fat_entry); + put_unaligned_le16(content, fat_entry); + } + } else { /* even */ + fat_sector[off] = (u8)(content); + + if (off == (u32)(sb->s_blocksize-1)) { + fat_sector[off] = (u8)(content); + if (fcache_modify(sb, sec)) + return -EIO; + + fat_sector = fcache_getblk(sb, ++sec); + if (!fat_sector) + return -EIO; + + fat_sector[0] = (u8)((fat_sector[0] & 0xF0) | (content >> 8)); + } else { + fat_entry = &(fat_sector[off]); + content |= 0xF000 & get_unaligned_le16(fat_entry); + put_unaligned_le16(content, fat_entry); + } + } + return fcache_modify(sb, sec); +} + + +static FATENT_OPS_T fat12_ent_ops = { + fat12_ent_get, + fat12_ent_set +}; + +static FATENT_OPS_T fat16_ent_ops = { + fat16_ent_get, + fat16_ent_set +}; + +static FATENT_OPS_T fat32_ent_ops = { + fat32_ent_get, + fat32_ent_set +}; + +static FATENT_OPS_T exfat_ent_ops = { + exfat_ent_get, + exfat_ent_set +}; + +s32 fat_ent_ops_init(struct super_block *sb) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + switch (fsi->vol_type) { + case EXFAT: + fsi->fatent_ops = &exfat_ent_ops; + break; + case FAT32: + fsi->fatent_ops = &fat32_ent_ops; + break; + case FAT16: + fsi->fatent_ops = &fat16_ent_ops; + break; + case FAT12: + fsi->fatent_ops = &fat12_ent_ops; + break; + default: + fsi->fatent_ops = NULL; + EMSG("Unknown volume type : %d", (int)fsi->vol_type); + return -ENOTSUPP; + } + + return 0; +} + +static inline bool is_reserved_clus(u32 clus) +{ + if (IS_CLUS_FREE(clus)) + return true; + if (IS_CLUS_EOF(clus)) + return true; + if (IS_CLUS_BAD(clus)) + return true; + return false; +} + +static inline bool is_valid_clus(FS_INFO_T *fsi, u32 clus) +{ + if (clus < CLUS_BASE || fsi->num_clusters <= clus) + return false; + return true; +} + +s32 fat_ent_get(struct super_block *sb, u32 loc, u32 *content) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + s32 err; + + if (!is_valid_clus(fsi, loc)) { + sdfat_fs_error(sb, "invalid access to FAT (entry 0x%08x)", loc); + return -EIO; + } + + err = fsi->fatent_ops->ent_get(sb, loc, content); + if (err) { + sdfat_fs_error(sb, "failed to access to FAT " + "(entry 0x%08x, err:%d)", loc, err); + return err; + } + + if (!is_reserved_clus(*content) && !is_valid_clus(fsi, *content)) { + sdfat_fs_error(sb, "invalid access to FAT (entry 0x%08x) " + "bogus content (0x%08x)", loc, *content); + return -EIO; + } + + return 0; +} + +s32 fat_ent_set(struct super_block *sb, u32 loc, u32 content) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + return fsi->fatent_ops->ent_set(sb, loc, content); +} + +s32 fat_ent_get_safe(struct super_block *sb, u32 loc, u32 *content) +{ + s32 err = fat_ent_get(sb, loc, content); + + if (err) + return err; + + if (IS_CLUS_FREE(*content)) { + sdfat_fs_error(sb, "invalid access to FAT free cluster " + "(entry 0x%08x)", loc); + return -EIO; + } + + if (IS_CLUS_BAD(*content)) { + sdfat_fs_error(sb, "invalid access to FAT bad cluster " + "(entry 0x%08x)", loc); + return -EIO; + } + + return 0; +} + +/* end of fatent.c */ diff --git a/fs/sdfat/misc.c b/fs/sdfat/misc.c new file mode 100644 index 0000000000000..b9592ef4ca98f --- /dev/null +++ b/fs/sdfat/misc.c @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* + * linux/fs/fat/misc.c + * + * Written 1992,1993 by Werner Almesberger + * 22/11/2000 - Fixed fat_date_unix2dos for dates earlier than 01/01/1980 + * and date_dos2unix for date==0 by Igor Zhbanov(bsg@uniyar.ac.ru) + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : misc.c */ +/* PURPOSE : Helper function for checksum and handing sdFAT error */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include +#include "sdfat.h" +#include "version.h" + +#ifdef CONFIG_SDFAT_SUPPORT_STLOG +#include +#else +#define ST_LOG(fmt, ...) +#endif + +/* + * sdfat_fs_error reports a file system problem that might indicate fa data + * corruption/inconsistency. Depending on 'errors' mount option the + * panic() is called, or error message is printed FAT and nothing is done, + * or filesystem is remounted read-only (default behavior). + * In case the file system is remounted read-only, it can be made writable + * again by remounting it. + */ +void __sdfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) +{ + struct sdfat_mount_options *opts = &SDFAT_SB(sb)->options; + va_list args; + struct va_format vaf; + struct block_device *bdev = sb->s_bdev; + dev_t bd_dev = bdev ? bdev->bd_dev : 0; + + if (report) { + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + pr_err("[SDFAT](%s[%d:%d]):ERR: %pV\n", + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf); +#ifdef CONFIG_SDFAT_SUPPORT_STLOG + if (opts->errors == SDFAT_ERRORS_RO && !(sb->s_flags & MS_RDONLY)) { + ST_LOG("[SDFAT](%s[%d:%d]):ERR: %pV\n", + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf); + } +#endif + va_end(args); + } + + if (opts->errors == SDFAT_ERRORS_PANIC) { + panic("[SDFAT](%s[%d:%d]): fs panic from previous error\n", + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev)); + } else if (opts->errors == SDFAT_ERRORS_RO && !(sb->s_flags & MS_RDONLY)) { + sb->s_flags |= MS_RDONLY; + pr_err("[SDFAT](%s[%d:%d]): Filesystem has been set " + "read-only\n", sb->s_id, MAJOR(bd_dev), MINOR(bd_dev)); +#ifdef CONFIG_SDFAT_SUPPORT_STLOG + ST_LOG("[SDFAT](%s[%d:%d]): Filesystem has been set read-only\n", + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev)); +#endif + } +} +EXPORT_SYMBOL(__sdfat_fs_error); + +/** + * __sdfat_msg() - print preformated SDFAT specific messages. + * All logs except what uses sdfat_fs_error() should be written by __sdfat_msg() + * If 'st' is set, the log is propagated to ST_LOG. + */ +void __sdfat_msg(struct super_block *sb, const char *level, int st, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + struct block_device *bdev = sb->s_bdev; + dev_t bd_dev = bdev ? bdev->bd_dev : 0; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + /* level means KERN_ pacility level */ + printk("%s[SDFAT](%s[%d:%d]): %pV\n", level, + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf); +#ifdef CONFIG_SDFAT_SUPPORT_STLOG + if (st) { + ST_LOG("[SDFAT](%s[%d:%d]): %pV\n", + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf); + } +#endif + va_end(args); +} +EXPORT_SYMBOL(__sdfat_msg); + +void sdfat_log_version(void) +{ + pr_info("[SDFAT] Filesystem version %s\n", SDFAT_VERSION); +#ifdef CONFIG_SDFAT_SUPPORT_STLOG + ST_LOG("[SDFAT] Filesystem version %s\n", SDFAT_VERSION); +#endif +} +EXPORT_SYMBOL(sdfat_log_version); + +/* externs sys_tz + * extern struct timezone sys_tz; + */ +#define UNIX_SECS_1980 315532800L + +#if BITS_PER_LONG == 64 +#define UNIX_SECS_2108 4354819200L +#endif + +/* days between 1970/01/01 and 1980/01/01 (2 leap days) */ +#define DAYS_DELTA_DECADE (365 * 10 + 2) +/* 120 (2100 - 1980) isn't leap year */ +#define NO_LEAP_YEAR_2100 (120) +#define IS_LEAP_YEAR(y) (!((y) & 0x3) && (y) != NO_LEAP_YEAR_2100) + +#define SECS_PER_MIN (60) +#define SECS_PER_HOUR (60 * SECS_PER_MIN) +#define SECS_PER_DAY (24 * SECS_PER_HOUR) + +#define MAKE_LEAP_YEAR(leap_year, year) \ + do { \ + /* 2100 isn't leap year */ \ + if (unlikely(year > NO_LEAP_YEAR_2100)) \ + leap_year = ((year + 3) / 4) - 1; \ + else \ + leap_year = ((year + 3) / 4); \ + } while (0) + +/* Linear day numbers of the respective 1sts in non-leap years. */ +static time_t accum_days_in_year[] = { + /* Month : N 01 02 03 04 05 06 07 08 09 10 11 12 */ + 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, +}; + +/* Convert a FAT time/date pair to a UNIX date (seconds since 1 1 70). */ +void sdfat_time_fat2unix(struct sdfat_sb_info *sbi, struct timespec *ts, + DATE_TIME_T *tp) +{ + time_t year = tp->Year; + time_t ld; /* leap day */ + + MAKE_LEAP_YEAR(ld, year); + + if (IS_LEAP_YEAR(year) && (tp->Month) > 2) + ld++; + + ts->tv_sec = tp->Second + tp->Minute * SECS_PER_MIN + + tp->Hour * SECS_PER_HOUR + + (year * 365 + ld + accum_days_in_year[tp->Month] + + (tp->Day - 1) + DAYS_DELTA_DECADE) * SECS_PER_DAY; + + if (!sbi->options.tz_utc) + ts->tv_sec += sys_tz.tz_minuteswest * SECS_PER_MIN; + + ts->tv_nsec = 0; +} + +/* Convert linear UNIX date to a FAT time/date pair. */ +void sdfat_time_unix2fat(struct sdfat_sb_info *sbi, struct timespec *ts, + DATE_TIME_T *tp) +{ + time_t second = ts->tv_sec; + time_t day, month, year; + time_t ld; /* leap day */ + + if (!sbi->options.tz_utc) + second -= sys_tz.tz_minuteswest * SECS_PER_MIN; + + /* Jan 1 GMT 00:00:00 1980. But what about another time zone? */ + if (second < UNIX_SECS_1980) { + tp->Second = 0; + tp->Minute = 0; + tp->Hour = 0; + tp->Day = 1; + tp->Month = 1; + tp->Year = 0; + return; + } +#if (BITS_PER_LONG == 64) + if (second >= UNIX_SECS_2108) { + tp->Second = 59; + tp->Minute = 59; + tp->Hour = 23; + tp->Day = 31; + tp->Month = 12; + tp->Year = 127; + return; + } +#endif + + day = second / SECS_PER_DAY - DAYS_DELTA_DECADE; + year = day / 365; + + MAKE_LEAP_YEAR(ld, year); + if (year * 365 + ld > day) + year--; + + MAKE_LEAP_YEAR(ld, year); + day -= year * 365 + ld; + + if (IS_LEAP_YEAR(year) && day == accum_days_in_year[3]) { + month = 2; + } else { + if (IS_LEAP_YEAR(year) && day > accum_days_in_year[3]) + day--; + for (month = 1; month < 12; month++) { + if (accum_days_in_year[month + 1] > day) + break; + } + } + day -= accum_days_in_year[month]; + + tp->Second = second % SECS_PER_MIN; + tp->Minute = (second / SECS_PER_MIN) % 60; + tp->Hour = (second / SECS_PER_HOUR) % 24; + tp->Day = day + 1; + tp->Month = month; + tp->Year = year; +} + +TIMESTAMP_T *tm_now(struct sdfat_sb_info *sbi, TIMESTAMP_T *tp) +{ + struct timespec ts; + DATE_TIME_T dt; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) + ts = CURRENT_TIME_SEC; +#else + ktime_get_real_ts(&ts); +#endif + + sdfat_time_unix2fat(sbi, &ts, &dt); + + tp->year = dt.Year; + tp->mon = dt.Month; + tp->day = dt.Day; + tp->hour = dt.Hour; + tp->min = dt.Minute; + tp->sec = dt.Second; + + return tp; +} + +u8 calc_chksum_1byte(void *data, s32 len, u8 chksum) +{ + s32 i; + u8 *c = (u8 *) data; + + for (i = 0; i < len; i++, c++) + chksum = (((chksum & 1) << 7) | ((chksum & 0xFE) >> 1)) + *c; + + return chksum; +} + +u16 calc_chksum_2byte(void *data, s32 len, u16 chksum, s32 type) +{ + s32 i; + u8 *c = (u8 *) data; + + for (i = 0; i < len; i++, c++) { + if (((i == 2) || (i == 3)) && (type == CS_DIR_ENTRY)) + continue; + chksum = (((chksum & 1) << 15) | ((chksum & 0xFFFE) >> 1)) + (u16) *c; + } + return chksum; +} + +#ifdef CONFIG_SDFAT_TRACE_ELAPSED_TIME +struct timeval __t1, __t2; +u32 sdfat_time_current_usec(struct timeval *tv) +{ + do_gettimeofday(tv); + return (u32)(tv->tv_sec*1000000 + tv->tv_usec); +} +#endif /* CONFIG_SDFAT_TRACE_ELAPSED_TIME */ + +#ifdef CONFIG_SDFAT_DBG_CAREFUL +/* Check the consistency of i_size_ondisk (FAT32, or flags 0x01 only) */ +void sdfat_debug_check_clusters(struct inode *inode) +{ + unsigned int num_clusters; + volatile uint32_t tmp_fat_chain[50]; + volatile int tmp_i = 0; + volatile unsigned int num_clusters_org, tmp_i = 0; + CHAIN_T clu; + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + FS_INFO_T *fsi = &(SDFAT_SB(inode->i_sb)->fsi); + + if (SDFAT_I(inode)->i_size_ondisk == 0) + num_clusters = 0; + else + num_clusters = ((SDFAT_I(inode)->i_size_ondisk-1) >> fsi->cluster_size_bits) + 1; + + clu.dir = fid->start_clu; + clu.size = num_clusters; + clu.flags = fid->flags; + + num_clusters_org = num_clusters; + + if (clu.flags == 0x03) + return; + + while (num_clusters > 0) { + /* FAT chain logging */ + tmp_fat_chain[tmp_i] = clu.dir; + tmp_i++; + if (tmp_i >= 50) + tmp_i = 0; + + BUG_ON(IS_CLUS_EOF(clu.dir) || IS_CLUS_FREE(clu.dir)); + + if (get_next_clus_safe(inode->i_sb, &(clu.dir))) + EMSG("%s: failed to access to FAT\n"); + + num_clusters--; + } + + BUG_ON(!IS_CLUS_EOF(clu.dir)); +} + +#endif /* CONFIG_SDFAT_DBG_CAREFUL */ + +#ifdef CONFIG_SDFAT_DBG_MSG +void __sdfat_dmsg(int level, const char *fmt, ...) +{ +#ifdef CONFIG_SDFAT_DBG_SHOW_PID + struct va_format vaf; + va_list args; + + /* should check type */ + if (level > SDFAT_MSG_LEVEL) + return; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + /* fmt already includes KERN_ pacility level */ + printk("[%u] %pV", current->pid, &vaf); + va_end(args); +#else + va_list args; + + /* should check type */ + if (level > SDFAT_MSG_LEVEL) + return; + + va_start(args, fmt); + /* fmt already includes KERN_ pacility level */ + vprintk(fmt, args); + va_end(args); +#endif +} +#endif diff --git a/fs/sdfat/mpage.c b/fs/sdfat/mpage.c new file mode 100644 index 0000000000000..b3985825815b6 --- /dev/null +++ b/fs/sdfat/mpage.c @@ -0,0 +1,622 @@ +/* + * fs/mpage.c + * + * Copyright (C) 2002, Linus Torvalds. + * + * Contains functions related to preparing and submitting BIOs which contain + * multiple pagecache pages. + * + * 15May2002 Andrew Morton + * Initial version + * 27Jun2002 axboe@suse.de + * use bio_add_page() to build bio's just the right size + */ + +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : core.c */ +/* PURPOSE : sdFAT glue layer for supporting VFS */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for mark_page_accessed() */ +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#include +#endif + +#include "sdfat.h" + +#ifdef CONFIG_SDFAT_ALIGNED_MPAGE_WRITE + +/************************************************************************* + * INNER FUNCTIONS FOR FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY + *************************************************************************/ +static void __mpage_write_end_io(struct bio *bio, int err); + +/************************************************************************* + * FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY + *************************************************************************/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) +static inline void __sdfat_submit_bio_write2(int flags, struct bio *bio) +{ + bio_set_op_attrs(bio, REQ_OP_WRITE, flags); + submit_bio(bio); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) */ +static inline void __sdfat_submit_bio_write2(int flags, struct bio *bio) +{ + submit_bio(WRITE | flags, bio); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) +static void mpage_write_end_io(struct bio *bio) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) + __mpage_write_end_io(bio, bio->bi_status); +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) */ + __mpage_write_end_io(bio, bio->bi_error); +#endif +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) */ +static void mpage_write_end_io(struct bio *bio, int err) +{ + if (test_bit(BIO_UPTODATE, &bio->bi_flags)) + err = 0; + __mpage_write_end_io(bio, err); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +static inline int bio_get_nr_vecs(struct block_device *bdev) +{ + return BIO_MAX_PAGES; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4,1,0) */ + /* EMPTY */ +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) +static inline sector_t __sdfat_bio_sector(struct bio *bio) +{ + return bio->bi_iter.bi_sector; +} + +static inline void __sdfat_set_bio_sector(struct bio *bio, sector_t sector) +{ + bio->bi_iter.bi_sector = sector; +} + +static inline unsigned int __sdfat_bio_size(struct bio *bio) +{ + return bio->bi_iter.bi_size; +} + +static inline void __sdfat_set_bio_size(struct bio *bio, unsigned int size) +{ + bio->bi_iter.bi_size = size; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) */ +static inline sector_t __sdfat_bio_sector(struct bio *bio) +{ + return bio->bi_sector; +} + +static inline void __sdfat_set_bio_sector(struct bio *bio, sector_t sector) +{ + bio->bi_sector = sector; +} + +static inline unsigned int __sdfat_bio_size(struct bio *bio) +{ + return bio->bi_size; +} + +static inline void __sdfat_set_bio_size(struct bio *bio, unsigned int size) +{ + bio->bi_size = size; +} +#endif + +/* __check_dfr_on() and __dfr_writepage_end_io() functions + * are copied from sdfat.c + * Each function should be same perfectly + */ +static inline int __check_dfr_on(struct inode *inode, loff_t start, loff_t end, const char *fname) +{ +#ifdef CONFIG_SDFAT_DFR + struct defrag_info *ino_dfr = &(SDFAT_I(inode)->dfr_info); + + if ((atomic_read(&ino_dfr->stat) == DFR_INO_STAT_REQ) && + fsapi_dfr_check_dfr_on(inode, start, end, 0, fname)) + return 1; +#endif + return 0; +} + +static inline int __dfr_writepage_end_io(struct page *page) +{ +#ifdef CONFIG_SDFAT_DFR + struct defrag_info *ino_dfr = &(SDFAT_I(page->mapping->host)->dfr_info); + + if (atomic_read(&ino_dfr->stat) == DFR_INO_STAT_REQ) + fsapi_dfr_writepage_endio(page); +#endif + return 0; +} + + +static inline unsigned int __calc_size_to_align(struct super_block *sb) +{ + struct block_device *bdev = sb->s_bdev; + struct gendisk *disk; + struct request_queue *queue; + struct queue_limits *limit; + unsigned int max_sectors; + unsigned int aligned = 0; + + disk = bdev->bd_disk; + if (!disk) + goto out; + + queue = disk->queue; + if (!queue) + goto out; + + limit = &queue->limits; + max_sectors = limit->max_sectors; + aligned = 1 << ilog2(max_sectors); + + if (aligned && (max_sectors & (aligned - 1))) + aligned = 0; +out: + return aligned; +} + +struct mpage_data { + struct bio *bio; + sector_t last_block_in_bio; + get_block_t *get_block; + unsigned int use_writepage; + unsigned int size_to_align; +}; + +/* + * I/O completion handler for multipage BIOs. + * + * The mpage code never puts partial pages into a BIO (except for end-of-file). + * If a page does not map to a contiguous run of blocks then it simply falls + * back to block_read_full_page(). + * + * Why is this? If a page's completion depends on a number of different BIOs + * which can complete in any order (or at the same time) then determining the + * status of that page is hard. See end_buffer_async_read() for the details. + * There is no point in duplicating all that complexity. + */ +static void __mpage_write_end_io(struct bio *bio, int err) +{ + struct bio_vec *bvec = bio->bi_io_vec + bio->bi_vcnt - 1; + + ASSERT(bio_data_dir(bio) == WRITE); /* only write */ + + do { + struct page *page = bvec->bv_page; + + if (--bvec >= bio->bi_io_vec) + prefetchw(&bvec->bv_page->flags); + if (err) { + SetPageError(page); + if (page->mapping) + mapping_set_error(page->mapping, err); + } + + __dfr_writepage_end_io(page); + + end_page_writeback(page); + } while (bvec >= bio->bi_io_vec); + bio_put(bio); +} + +static struct bio *mpage_bio_submit_write(int flags, struct bio *bio) +{ + bio->bi_end_io = mpage_write_end_io; + __sdfat_submit_bio_write2(flags, bio); + return NULL; +} + +static struct bio * +mpage_alloc(struct block_device *bdev, + sector_t first_sector, int nr_vecs, + gfp_t gfp_flags) +{ + struct bio *bio; + + bio = bio_alloc(gfp_flags, nr_vecs); + + if (bio == NULL && (current->flags & PF_MEMALLOC)) { + while (!bio && (nr_vecs /= 2)) + bio = bio_alloc(gfp_flags, nr_vecs); + } + + if (bio) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + bio_set_dev(bio, bdev); +#else + bio->bi_bdev = bdev; +#endif + __sdfat_set_bio_sector(bio, first_sector); + } + return bio; +} + +static int sdfat_mpage_writepage(struct page *page, + struct writeback_control *wbc, void *data) +{ + struct mpage_data *mpd = data; + struct bio *bio = mpd->bio; + struct address_space *mapping = page->mapping; + struct inode *inode = page->mapping->host; + const unsigned int blkbits = inode->i_blkbits; + const unsigned int blocks_per_page = PAGE_SIZE >> blkbits; + sector_t last_block; + sector_t block_in_file; + sector_t blocks[MAX_BUF_PER_PAGE]; + unsigned int page_block; + unsigned int first_unmapped = blocks_per_page; + struct block_device *bdev = NULL; + int boundary = 0; + sector_t boundary_block = 0; + struct block_device *boundary_bdev = NULL; + int length; + struct buffer_head map_bh; + loff_t i_size = i_size_read(inode); + unsigned long end_index = i_size >> PAGE_SHIFT; + int ret = 0; + + if (page_has_buffers(page)) { + struct buffer_head *head = page_buffers(page); + struct buffer_head *bh = head; + + /* If they're all mapped and dirty, do it */ + page_block = 0; + do { + BUG_ON(buffer_locked(bh)); + if (!buffer_mapped(bh)) { + /* + * unmapped dirty buffers are created by + * __set_page_dirty_buffers -> mmapped data + */ + if (buffer_dirty(bh)) + goto confused; + if (first_unmapped == blocks_per_page) + first_unmapped = page_block; + continue; + } + + if (first_unmapped != blocks_per_page) + goto confused; /* hole -> non-hole */ + + if (!buffer_dirty(bh) || !buffer_uptodate(bh)) + goto confused; + + /* bh should be mapped if delay is set */ + if (buffer_delay(bh)) { + sector_t blk_in_file = + (sector_t)(page->index << (PAGE_SHIFT - blkbits)) + page_block; + + BUG_ON(bh->b_size != (1 << blkbits)); + if (page->index > end_index) { + MMSG("%s(inode:%p) " + "over end with delayed buffer" + "(page_idx:%u, end_idx:%u)\n", + __func__, inode, + (u32)page->index, + (u32)end_index); + goto confused; + } + + ret = mpd->get_block(inode, blk_in_file, bh, 1); + if (ret) { + MMSG("%s(inode:%p) " + "failed to getblk(ret:%d)\n", + __func__, inode, ret); + goto confused; + } + + BUG_ON(buffer_delay(bh)); + + if (buffer_new(bh)) { + clear_buffer_new(bh); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + clean_bdev_bh_alias(bh); +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) */ + unmap_underlying_metadata(bh->b_bdev, bh->b_blocknr); +#endif + } + } + + if (page_block) { + if (bh->b_blocknr != blocks[page_block-1] + 1) { + MMSG("%s(inode:%p) pblk(%d) " + "no_seq(prev:%lld, new:%lld)\n", + __func__, inode, page_block, + (u64)blocks[page_block-1], + (u64)bh->b_blocknr); + goto confused; + } + } + blocks[page_block++] = bh->b_blocknr; + boundary = buffer_boundary(bh); + if (boundary) { + boundary_block = bh->b_blocknr; + boundary_bdev = bh->b_bdev; + } + bdev = bh->b_bdev; + } while ((bh = bh->b_this_page) != head); + + if (first_unmapped) + goto page_is_mapped; + + /* + * Page has buffers, but they are all unmapped. The page was + * created by pagein or read over a hole which was handled by + * block_read_full_page(). If this address_space is also + * using mpage_readpages then this can rarely happen. + */ + goto confused; + } + + /* + * The page has no buffers: map it to disk + */ + BUG_ON(!PageUptodate(page)); + block_in_file = (sector_t)page->index << (PAGE_SHIFT - blkbits); + last_block = (i_size - 1) >> blkbits; + map_bh.b_page = page; + for (page_block = 0; page_block < blocks_per_page; ) { + + map_bh.b_state = 0; + map_bh.b_size = 1 << blkbits; + if (mpd->get_block(inode, block_in_file, &map_bh, 1)) + goto confused; + + if (buffer_new(&map_bh)) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + clean_bdev_bh_alias(&map_bh); +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) */ + unmap_underlying_metadata(map_bh.b_bdev, + map_bh.b_blocknr); +#endif + if (buffer_boundary(&map_bh)) { + boundary_block = map_bh.b_blocknr; + boundary_bdev = map_bh.b_bdev; + } + + if (page_block) { + if (map_bh.b_blocknr != blocks[page_block-1] + 1) + goto confused; + } + blocks[page_block++] = map_bh.b_blocknr; + boundary = buffer_boundary(&map_bh); + bdev = map_bh.b_bdev; + if (block_in_file == last_block) + break; + block_in_file++; + } + BUG_ON(page_block == 0); + + first_unmapped = page_block; + +page_is_mapped: + if (page->index >= end_index) { + /* + * The page straddles i_size. It must be zeroed out on each + * and every writepage invocation because it may be mmapped. + * "A file is mapped in multiples of the page size. For a file + * that is not a multiple of the page size, the remaining memory + * is zeroed when mapped, and writes to that region are not + * written out to the file." + */ + unsigned int offset = i_size & (PAGE_SIZE - 1); + + if (page->index > end_index || !offset) { + MMSG("%s(inode:%p) over end " + "(page_idx:%u, end_idx:%u off:%u)\n", + __func__, inode, (u32)page->index, + (u32)end_index, (u32)offset); + goto confused; + } + zero_user_segment(page, offset, PAGE_SIZE); + } + + /* + * This page will go to BIO. Do we need to send this BIO off first? + * + * REMARK : added ELSE_IF for ALIGNMENT_MPAGE_WRITE of SDFAT + */ + if (bio) { + if (mpd->last_block_in_bio != blocks[0] - 1) { + bio = mpage_bio_submit_write(0, bio); + } else if (mpd->size_to_align) { + unsigned int mask = mpd->size_to_align - 1; + sector_t max_end_block = + (__sdfat_bio_sector(bio) & ~(mask)) + mask; + + if ((__sdfat_bio_size(bio) != (1 << (mask + 1))) && + (mpd->last_block_in_bio == max_end_block)) { + MMSG("%s(inode:%p) alignment mpage_bio_submit" + "(start:%u, len:%u aligned:%u)\n", + __func__, inode, + (unsigned int)__sdfat_bio_sector(bio), + (unsigned int)(mpd->last_block_in_bio - + __sdfat_bio_sector(bio) + 1), + (unsigned int)mpd->size_to_align); + bio = mpage_bio_submit_write(REQ_NOMERGE, bio); + } + } + } + +alloc_new: + if (!bio) { + bio = mpage_alloc(bdev, blocks[0] << (blkbits - 9), + bio_get_nr_vecs(bdev), GFP_NOFS|__GFP_HIGH); + if (!bio) + goto confused; + } + + /* + * Must try to add the page before marking the buffer clean or + * the confused fail path above (OOM) will be very confused when + * it finds all bh marked clean (i.e. it will not write anything) + */ + length = first_unmapped << blkbits; + if (bio_add_page(bio, page, length, 0) < length) { + bio = mpage_bio_submit_write(0, bio); + goto alloc_new; + } + + /* + * OK, we have our BIO, so we can now mark the buffers clean. Make + * sure to only clean buffers which we know we'll be writing. + */ + if (page_has_buffers(page)) { + struct buffer_head *head = page_buffers(page); + struct buffer_head *bh = head; + unsigned int buffer_counter = 0; + + do { + if (buffer_counter++ == first_unmapped) + break; + clear_buffer_dirty(bh); + bh = bh->b_this_page; + } while (bh != head); + + /* + * we cannot drop the bh if the page is not uptodate + * or a concurrent readpage would fail to serialize with the bh + * and it would read from disk before we reach the platter. + */ + if (buffer_heads_over_limit && PageUptodate(page)) + try_to_free_buffers(page); + } + + BUG_ON(PageWriteback(page)); + set_page_writeback(page); + + /* + * FIXME FOR DEFRAGMENTATION : CODE REVIEW IS REQUIRED + * + * Turn off MAPPED flag in victim's bh if defrag on. + * Another write_begin can starts after get_block for defrag victims + * called. + * In this case, write_begin calls get_block and get original block + * number and previous defrag will be canceled. + */ + if (unlikely(__check_dfr_on(inode, (loff_t)(page->index << PAGE_SHIFT), + (loff_t)((page->index + 1) << PAGE_SHIFT), __func__))) { + struct buffer_head *head = page_buffers(page); + struct buffer_head *bh = head; + + do { + clear_buffer_mapped(bh); + bh = bh->b_this_page; + } while (bh != head); + } + + unlock_page(page); + if (boundary || (first_unmapped != blocks_per_page)) { + bio = mpage_bio_submit_write(0, bio); + if (boundary_block) { + write_boundary_block(boundary_bdev, + boundary_block, 1 << blkbits); + } + } else { + mpd->last_block_in_bio = blocks[blocks_per_page - 1]; + } + + goto out; + +confused: + if (bio) + bio = mpage_bio_submit_write(0, bio); + + if (mpd->use_writepage) { + ret = mapping->a_ops->writepage(page, wbc); + } else { + ret = -EAGAIN; + goto out; + } + /* + * The caller has a ref on the inode, so *mapping is stable + */ + mapping_set_error(mapping, ret); +out: + mpd->bio = bio; + return ret; +} + +int sdfat_mpage_writepages(struct address_space *mapping, + struct writeback_control *wbc, get_block_t *get_block) +{ + struct blk_plug plug; + int ret; + struct mpage_data mpd = { + .bio = NULL, + .last_block_in_bio = 0, + .get_block = get_block, + .use_writepage = 1, + .size_to_align = __calc_size_to_align(mapping->host->i_sb), + }; + + BUG_ON(!get_block); + blk_start_plug(&plug); + ret = write_cache_pages(mapping, wbc, sdfat_mpage_writepage, &mpd); + if (mpd.bio) + mpage_bio_submit_write(0, mpd.bio); + blk_finish_plug(&plug); + return ret; +} + +#endif /* CONFIG_SDFAT_ALIGNED_MPAGE_WRITE */ diff --git a/fs/sdfat/nls.c b/fs/sdfat/nls.c new file mode 100644 index 0000000000000..b65634454c557 --- /dev/null +++ b/fs/sdfat/nls.c @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : nls.c */ +/* PURPOSE : sdFAT NLS Manager */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ +#include +#include + +#include "sdfat.h" +#include "core.h" + +/*----------------------------------------------------------------------*/ +/* Global Variable Definitions */ +/*----------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------*/ +/* Local Variable Definitions */ +/*----------------------------------------------------------------------*/ + +static u16 bad_dos_chars[] = { + /* + , ; = [ ] */ + 0x002B, 0x002C, 0x003B, 0x003D, 0x005B, 0x005D, + 0xFF0B, 0xFF0C, 0xFF1B, 0xFF1D, 0xFF3B, 0xFF3D, + 0 +}; + +/* + * Allow full-width illegal characters : + * "MS windows 7" supports full-width-invalid-name-characters. + * So we should check half-width-invalid-name-characters(ASCII) only + * for compatibility. + * + * " * / : < > ? \ | + * + * patch 1.2.0 + */ +static u16 bad_uni_chars[] = { + 0x0022, 0x002A, 0x002F, 0x003A, + 0x003C, 0x003E, 0x003F, 0x005C, 0x007C, +#if 0 /* allow full-width characters */ + 0x201C, 0x201D, 0xFF0A, 0xFF0F, 0xFF1A, + 0xFF1C, 0xFF1E, 0xFF1F, 0xFF3C, 0xFF5C, +#endif + 0 +}; + +/*----------------------------------------------------------------------*/ +/* Local Function Declarations */ +/*----------------------------------------------------------------------*/ +static s32 convert_uni_to_ch(struct nls_table *nls, u16 uni, u8 *ch, s32 *lossy); +static s32 convert_ch_to_uni(struct nls_table *nls, u8 *ch, u16 *uni, s32 *lossy); + +static u16 nls_upper(struct super_block *sb, u16 a) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + if (SDFAT_SB(sb)->options.casesensitive) + return a; + if ((fsi->vol_utbl)[get_col_index(a)] != NULL) + return (fsi->vol_utbl)[get_col_index(a)][get_row_index(a)]; + else + return a; +} +/*======================================================================*/ +/* Global Function Definitions */ +/*======================================================================*/ +u16 *nls_wstrchr(u16 *str, u16 wchar) +{ + while (*str) { + if (*(str++) == wchar) + return str; + } + + return 0; +} + +s32 nls_cmp_sfn(struct super_block *sb, u8 *a, u8 *b) +{ + return strncmp((void *)a, (void *)b, DOS_NAME_LENGTH); +} + +s32 nls_cmp_uniname(struct super_block *sb, u16 *a, u16 *b) +{ + s32 i; + + for (i = 0; i < MAX_NAME_LENGTH; i++, a++, b++) { + if (nls_upper(sb, *a) != nls_upper(sb, *b)) + return 1; + if (*a == 0x0) + return 0; + } + return 0; +} + +#define CASE_LOWER_BASE (0x08) /* base is lower case */ +#define CASE_LOWER_EXT (0x10) /* extension is lower case */ + +s32 nls_uni16s_to_sfn(struct super_block *sb, UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname, s32 *p_lossy) +{ + s32 i, j, len, lossy = NLS_NAME_NO_LOSSY; + u8 buf[MAX_CHARSET_SIZE]; + u8 lower = 0, upper = 0; + u8 *dosname = p_dosname->name; + u16 *uniname = p_uniname->name; + u16 *p, *last_period; + struct nls_table *nls = SDFAT_SB(sb)->nls_disk; + + /* DOSNAME is filled with space */ + for (i = 0; i < DOS_NAME_LENGTH; i++) + *(dosname+i) = ' '; + + /* DOT and DOTDOT are handled by VFS layer */ + + /* search for the last embedded period */ + last_period = NULL; + for (p = uniname; *p; p++) { + if (*p == (u16) '.') + last_period = p; + } + + i = 0; + while (i < DOS_NAME_LENGTH) { + if (i == 8) { + if (last_period == NULL) + break; + + if (uniname <= last_period) { + if (uniname < last_period) + lossy |= NLS_NAME_OVERLEN; + uniname = last_period + 1; + } + } + + if (*uniname == (u16) '\0') { + break; + } else if (*uniname == (u16) ' ') { + lossy |= NLS_NAME_LOSSY; + } else if (*uniname == (u16) '.') { + if (uniname < last_period) + lossy |= NLS_NAME_LOSSY; + else + i = 8; + } else if (nls_wstrchr(bad_dos_chars, *uniname)) { + lossy |= NLS_NAME_LOSSY; + *(dosname+i) = '_'; + i++; + } else { + len = convert_uni_to_ch(nls, *uniname, buf, &lossy); + + if (len > 1) { + if ((i >= 8) && ((i+len) > DOS_NAME_LENGTH)) + break; + + if ((i < 8) && ((i+len) > 8)) { + i = 8; + continue; + } + + lower = 0xFF; + + for (j = 0; j < len; j++, i++) + *(dosname+i) = *(buf+j); + } else { /* len == 1 */ + if ((*buf >= 'a') && (*buf <= 'z')) { + *(dosname+i) = *buf - ('a' - 'A'); + + lower |= (i < 8) ? + CASE_LOWER_BASE : + CASE_LOWER_EXT; + } else if ((*buf >= 'A') && (*buf <= 'Z')) { + *(dosname+i) = *buf; + + upper |= (i < 8) ? + CASE_LOWER_BASE : + CASE_LOWER_EXT; + } else { + *(dosname+i) = *buf; + } + i++; + } + } + + uniname++; + } + + if (*dosname == 0xE5) + *dosname = 0x05; + if (*uniname != 0x0) + lossy |= NLS_NAME_OVERLEN; + + if (upper & lower) + p_dosname->name_case = 0xFF; + else + p_dosname->name_case = lower; + + if (p_lossy) + *p_lossy = lossy; + return i; +} + +s32 nls_sfn_to_uni16s(struct super_block *sb, DOS_NAME_T *p_dosname, UNI_NAME_T *p_uniname) +{ + s32 i = 0, j, n = 0; + u8 buf[MAX_DOSNAME_BUF_SIZE]; + u8 *dosname = p_dosname->name; + u16 *uniname = p_uniname->name; + struct nls_table *nls = SDFAT_SB(sb)->nls_disk; + + if (*dosname == 0x05) { + *buf = 0xE5; + i++; + n++; + } + + for ( ; i < 8; i++, n++) { + if (*(dosname+i) == ' ') + break; + + if ((*(dosname+i) >= 'A') && (*(dosname+i) <= 'Z') && + (p_dosname->name_case & CASE_LOWER_BASE)) + *(buf+n) = *(dosname+i) + ('a' - 'A'); + else + *(buf+n) = *(dosname+i); + } + if (*(dosname+8) != ' ') { + *(buf+n) = '.'; + n++; + } + + for (i = 8; i < DOS_NAME_LENGTH; i++, n++) { + if (*(dosname+i) == ' ') + break; + + if ((*(dosname+i) >= 'A') && (*(dosname+i) <= 'Z') && + (p_dosname->name_case & CASE_LOWER_EXT)) + *(buf+n) = *(dosname+i) + ('a' - 'A'); + else + *(buf+n) = *(dosname+i); + } + *(buf+n) = '\0'; + + i = j = 0; + while (j < MAX_NAME_LENGTH) { + if (*(buf+i) == '\0') + break; + + i += convert_ch_to_uni(nls, (buf+i), uniname, NULL); + + uniname++; + j++; + } + + *uniname = (u16) '\0'; + return j; +} + +static s32 __nls_utf16s_to_vfsname(struct super_block *sb, UNI_NAME_T *p_uniname, u8 *p_cstring, s32 buflen) +{ + s32 len; + const u16 *uniname = p_uniname->name; + + /* always len >= 0 */ + len = utf16s_to_utf8s(uniname, MAX_NAME_LENGTH, UTF16_HOST_ENDIAN, + p_cstring, buflen); + p_cstring[len] = '\0'; + return len; +} + +static s32 __nls_vfsname_to_utf16s(struct super_block *sb, const u8 *p_cstring, + const s32 len, UNI_NAME_T *p_uniname, s32 *p_lossy) +{ + s32 i, unilen, lossy = NLS_NAME_NO_LOSSY; + u16 upname[MAX_NAME_LENGTH+1]; + u16 *uniname = p_uniname->name; + + BUG_ON(!len); + + unilen = utf8s_to_utf16s(p_cstring, len, UTF16_HOST_ENDIAN, + (wchar_t *)uniname, MAX_NAME_LENGTH+2); + if (unilen < 0) { + MMSG("%s: failed to vfsname_to_utf16(err:%d) " + "vfsnamelen:%d", __func__, unilen, len); + return unilen; + } + + if (unilen > MAX_NAME_LENGTH) { + MMSG("%s: failed to vfsname_to_utf16(estr:ENAMETOOLONG) " + "vfsnamelen:%d, unilen:%d>%d", + __func__, len, unilen, MAX_NAME_LENGTH); + return -ENAMETOOLONG; + } + + p_uniname->name_len = (u8)(unilen & 0xFF); + + for (i = 0; i < unilen; i++) { + if ((*uniname < 0x0020) || nls_wstrchr(bad_uni_chars, *uniname)) + lossy |= NLS_NAME_LOSSY; + + *(upname+i) = nls_upper(sb, *uniname); + uniname++; + } + + *uniname = (u16)'\0'; + p_uniname->name_len = unilen; + p_uniname->name_hash = calc_chksum_2byte((void *) upname, + unilen << 1, 0, CS_DEFAULT); + + if (p_lossy) + *p_lossy = lossy; + + return unilen; +} + +static s32 __nls_uni16s_to_vfsname(struct super_block *sb, UNI_NAME_T *p_uniname, u8 *p_cstring, s32 buflen) +{ + s32 i, j, len, out_len = 0; + u8 buf[MAX_CHARSET_SIZE]; + const u16 *uniname = p_uniname->name; + struct nls_table *nls = SDFAT_SB(sb)->nls_io; + + i = 0; + while ((i < MAX_NAME_LENGTH) && (out_len < (buflen-1))) { + if (*uniname == (u16)'\0') + break; + + len = convert_uni_to_ch(nls, *uniname, buf, NULL); + + if (out_len + len >= buflen) + len = (buflen - 1) - out_len; + + out_len += len; + + if (len > 1) { + for (j = 0; j < len; j++) + *p_cstring++ = (s8) *(buf+j); + } else { /* len == 1 */ + *p_cstring++ = (s8) *buf; + } + + uniname++; + i++; + } + + *p_cstring = '\0'; + return out_len; +} + +static s32 __nls_vfsname_to_uni16s(struct super_block *sb, const u8 *p_cstring, + const s32 len, UNI_NAME_T *p_uniname, s32 *p_lossy) +{ + s32 i, unilen, lossy = NLS_NAME_NO_LOSSY; + u16 upname[MAX_NAME_LENGTH+1]; + u16 *uniname = p_uniname->name; + struct nls_table *nls = SDFAT_SB(sb)->nls_io; + + BUG_ON(!len); + + i = unilen = 0; + while ((unilen < MAX_NAME_LENGTH) && (i < len)) { + i += convert_ch_to_uni(nls, (u8 *)(p_cstring+i), uniname, &lossy); + + if ((*uniname < 0x0020) || nls_wstrchr(bad_uni_chars, *uniname)) + lossy |= NLS_NAME_LOSSY; + + *(upname+unilen) = nls_upper(sb, *uniname); + + uniname++; + unilen++; + } + + if (*(p_cstring+i) != '\0') + lossy |= NLS_NAME_OVERLEN; + + *uniname = (u16)'\0'; + p_uniname->name_len = unilen; + p_uniname->name_hash = + calc_chksum_2byte((void *) upname, unilen<<1, 0, CS_DEFAULT); + + if (p_lossy) + *p_lossy = lossy; + + return unilen; +} + +s32 nls_uni16s_to_vfsname(struct super_block *sb, UNI_NAME_T *uniname, u8 *p_cstring, s32 buflen) +{ + if (SDFAT_SB(sb)->options.utf8) + return __nls_utf16s_to_vfsname(sb, uniname, p_cstring, buflen); + + return __nls_uni16s_to_vfsname(sb, uniname, p_cstring, buflen); +} + +s32 nls_vfsname_to_uni16s(struct super_block *sb, const u8 *p_cstring, const s32 len, UNI_NAME_T *uniname, s32 *p_lossy) +{ + if (SDFAT_SB(sb)->options.utf8) + return __nls_vfsname_to_utf16s(sb, p_cstring, len, uniname, p_lossy); + return __nls_vfsname_to_uni16s(sb, p_cstring, len, uniname, p_lossy); +} + +/*======================================================================*/ +/* Local Function Definitions */ +/*======================================================================*/ + +static s32 convert_ch_to_uni(struct nls_table *nls, u8 *ch, u16 *uni, s32 *lossy) +{ + int len; + + *uni = 0x0; + + if (ch[0] < 0x80) { + *uni = (u16) ch[0]; + return 1; + } + + len = nls->char2uni(ch, MAX_CHARSET_SIZE, uni); + if (len < 0) { + /* conversion failed */ + DMSG("%s: fail to use nls\n", __func__); + if (lossy != NULL) + *lossy |= NLS_NAME_LOSSY; + *uni = (u16) '_'; + if (!strcmp(nls->charset, "utf8")) + return 1; + return 2; + } + + return len; +} /* end of convert_ch_to_uni */ + +static s32 convert_uni_to_ch(struct nls_table *nls, u16 uni, u8 *ch, s32 *lossy) +{ + int len; + + ch[0] = 0x0; + + if (uni < 0x0080) { + ch[0] = (u8) uni; + return 1; + } + + len = nls->uni2char(uni, ch, MAX_CHARSET_SIZE); + if (len < 0) { + /* conversion failed */ + DMSG("%s: fail to use nls\n", __func__); + if (lossy != NULL) + *lossy |= NLS_NAME_LOSSY; + ch[0] = '_'; + return 1; + } + + return len; + +} /* end of convert_uni_to_ch */ + +/* end of nls.c */ diff --git a/fs/sdfat/sdfat.c b/fs/sdfat/sdfat.c new file mode 100644 index 0000000000000..d83bf369b4dee --- /dev/null +++ b/fs/sdfat/sdfat.c @@ -0,0 +1,5133 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : core.c */ +/* PURPOSE : sdFAT glue layer for supporting VFS */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for mark_page_accessed() */ +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0) +#error SDFAT only supports linux kernel version 3.0 or higher +#endif + +#include "sdfat.h" +#include "version.h" + +/* skip iterating emit_dots when dir is empty */ +#define ITER_POS_FILLED_DOTS (2) + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) +#define current_time(x) (CURRENT_TIME_SEC) +#endif +/* type index declare at sdfat.h */ +const char *FS_TYPE_STR[] = { + "auto", + "exfat", + "vfat" +}; + +static struct kset *sdfat_kset; +static struct kmem_cache *sdfat_inode_cachep; + + +static int sdfat_default_codepage = CONFIG_SDFAT_DEFAULT_CODEPAGE; +static char sdfat_default_iocharset[] = CONFIG_SDFAT_DEFAULT_IOCHARSET; +static const char sdfat_iocharset_with_utf8[] = "iso8859-1"; + +#ifdef CONFIG_SDFAT_TRACE_SB_LOCK +static unsigned long __lock_jiffies; +#endif + +static void sdfat_truncate(struct inode *inode, loff_t old_size); +static int sdfat_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create); + +static struct inode *sdfat_iget(struct super_block *sb, loff_t i_pos); +static struct inode *sdfat_build_inode(struct super_block *sb, const FILE_ID_T *fid, loff_t i_pos); +static void sdfat_detach(struct inode *inode); +static void sdfat_attach(struct inode *inode, loff_t i_pos); +static inline unsigned long sdfat_hash(loff_t i_pos); +static int __sdfat_write_inode(struct inode *inode, int sync); +static int sdfat_sync_inode(struct inode *inode); +static int sdfat_write_inode(struct inode *inode, struct writeback_control *wbc); +static void sdfat_write_super(struct super_block *sb); +static void sdfat_write_failed(struct address_space *mapping, loff_t to); + +static void sdfat_init_namebuf(DENTRY_NAMEBUF_T *nb); +static int sdfat_alloc_namebuf(DENTRY_NAMEBUF_T *nb); +static void sdfat_free_namebuf(DENTRY_NAMEBUF_T *nb); + +/************************************************************************* + * INNER FUNCTIONS FOR FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY + *************************************************************************/ +static void __sdfat_writepage_end_io(struct bio *bio, int err); +static inline void __lock_super(struct super_block *sb); +static inline void __unlock_super(struct super_block *sb); +static int __sdfat_create(struct inode *dir, struct dentry *dentry); +static int __sdfat_revalidate(struct dentry *dentry); +static int __sdfat_revalidate_ci(struct dentry *dentry, unsigned int flags); +static int __sdfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync); +static struct dentry *__sdfat_lookup(struct inode *dir, struct dentry *dentry); +static int __sdfat_mkdir(struct inode *dir, struct dentry *dentry); +static int __sdfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry); +static int __sdfat_show_options(struct seq_file *m, struct super_block *sb); +static inline ssize_t __sdfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs); +static inline ssize_t __sdfat_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + loff_t count, unsigned long nr_segs); +static int __sdfat_d_hash(const struct dentry *dentry, struct qstr *qstr); +static int __sdfat_d_hashi(const struct dentry *dentry, struct qstr *qstr); +static int __sdfat_cmp(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name); +static int __sdfat_cmpi(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name); + +/************************************************************************* + * FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY + *************************************************************************/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +static int sdfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ + /* + * The VFS already checks for existence, so for local filesystems + * the RENAME_NOREPLACE implementation is equivalent to plain rename. + * Don't support any other flags + */ + if (flags & ~RENAME_NOREPLACE) + return -EINVAL; + return __sdfat_rename(old_dir, old_dentry, new_dir, new_dentry); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) */ +static int sdfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + return __sdfat_rename(old_dir, old_dentry, new_dir, new_dentry); +} + +static int setattr_prepare(struct dentry *dentry, struct iattr *attr) +{ + struct inode *inode = dentry->d_inode; + + return inode_change_ok(inode, attr); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) +static inline void __sdfat_submit_bio_write(struct bio *bio) +{ + bio_set_op_attrs(bio, REQ_OP_WRITE, 0); + submit_bio(bio); +} + +static inline unsigned int __sdfat_full_name_hash(const struct dentry *dentry, const char *name, unsigned int len) +{ + return full_name_hash(dentry, name, len); +} + +static inline unsigned long __sdfat_init_name_hash(const struct dentry *dentry) +{ + return init_name_hash(dentry); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) */ +static inline void __sdfat_submit_bio_write(struct bio *bio) +{ + submit_bio(WRITE, bio); +} + +static inline unsigned int __sdfat_full_name_hash(const struct dentry *unused, const char *name, unsigned int len) +{ + return full_name_hash(name, len); +} + +static inline unsigned long __sdfat_init_name_hash(const struct dentry *unused) +{ + return init_name_hash(); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 21) + /* EMPTY */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 21) */ +static inline void inode_lock(struct inode *inode) +{ + mutex_lock(&inode->i_mutex); +} + +static inline void inode_unlock(struct inode *inode) +{ + mutex_unlock(&inode->i_mutex); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) +static void sdfat_writepage_end_io(struct bio *bio) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) + __sdfat_writepage_end_io(bio, bio->bi_status); +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) */ + __sdfat_writepage_end_io(bio, bio->bi_error); +#endif +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) */ +static void sdfat_writepage_end_io(struct bio *bio, int err) +{ + if (test_bit(BIO_UPTODATE, &bio->bi_flags)) + err = 0; + __sdfat_writepage_end_io(bio, err); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) +static inline int sdfat_remount_syncfs(struct super_block *sb) +{ + sync_filesystem(sb); + return 0; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) */ +static inline int sdfat_remount_syncfs(struct super_block *sb) +{ + /* + * We don`t need to call sync_filesystem(sb), + * Because VFS calls it. + */ + return 0; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) +static inline sector_t __sdfat_bio_sector(struct bio *bio) +{ + return bio->bi_iter.bi_sector; +} + +static inline void __sdfat_set_bio_iterate(struct bio *bio, sector_t sector, + unsigned int size, unsigned int idx, unsigned int done) +{ + struct bvec_iter *iter = &(bio->bi_iter); + + iter->bi_sector = sector; + iter->bi_size = size; + iter->bi_idx = idx; + iter->bi_bvec_done = done; +} + +static void __sdfat_truncate_pagecache(struct inode *inode, + loff_t to, loff_t newsize) +{ + truncate_pagecache(inode, newsize); +} + +static int sdfat_d_hash(const struct dentry *dentry, struct qstr *qstr) +{ + return __sdfat_d_hash(dentry, qstr); +} + +static int sdfat_d_hashi(const struct dentry *dentry, struct qstr *qstr) +{ + return __sdfat_d_hashi(dentry, qstr); +} + +//instead of sdfat_readdir +static int sdfat_iterate(struct file *filp, struct dir_context *ctx) +{ + struct inode *inode = filp->f_path.dentry->d_inode; + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + DIR_ENTRY_T de; + DENTRY_NAMEBUF_T *nb = &(de.NameBuf); + unsigned long inum; + loff_t cpos; + int err = 0, fake_offset = 0; + + sdfat_init_namebuf(nb); + __lock_super(sb); + + cpos = ctx->pos; + if ((fsi->vol_type == EXFAT) || (inode->i_ino == SDFAT_ROOT_INO)) { + if (!dir_emit_dots(filp, ctx)) + goto out; + if (ctx->pos == ITER_POS_FILLED_DOTS) { + cpos = 0; + fake_offset = 1; + } + } + if (cpos & (DENTRY_SIZE - 1)) { + err = -ENOENT; + goto out; + } + + /* name buffer should be allocated before use */ + err = sdfat_alloc_namebuf(nb); + if (err) + goto out; +get_new: + SDFAT_I(inode)->fid.size = i_size_read(inode); + SDFAT_I(inode)->fid.rwoffset = cpos >> DENTRY_SIZE_BITS; + + if (cpos >= SDFAT_I(inode)->fid.size) + goto end_of_dir; + + err = fsapi_readdir(inode, &de); + if (err) { + // at least we tried to read a sector + // move cpos to next sector position (should be aligned) + if (err == -EIO) { + cpos += 1 << (sb->s_blocksize_bits); + cpos &= ~((u32)sb->s_blocksize-1); + } + + err = -EIO; + goto end_of_dir; + } + + cpos = SDFAT_I(inode)->fid.rwoffset << DENTRY_SIZE_BITS; + + if (!nb->lfn[0]) + goto end_of_dir; + + if (!memcmp(nb->sfn, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = inode->i_ino; + } else if (!memcmp(nb->sfn, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = parent_ino(filp->f_path.dentry); + } else { + loff_t i_pos = ((loff_t) SDFAT_I(inode)->fid.start_clu << 32) | + ((SDFAT_I(inode)->fid.rwoffset-1) & 0xffffffff); + struct inode *tmp = sdfat_iget(sb, i_pos); + + if (tmp) { + inum = tmp->i_ino; + iput(tmp); + } else { + inum = iunique(sb, SDFAT_ROOT_INO); + } + } + + /* Before calling dir_emit(), sb_lock should be released. + * Because page fault can occur in dir_emit() when the size of buffer given + * from user is larger than one page size + */ + __unlock_super(sb); + if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, + (de.Attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) + goto out_unlocked; + __lock_super(sb); + + ctx->pos = cpos; + goto get_new; + +end_of_dir: + if (!cpos && fake_offset) + cpos = ITER_POS_FILLED_DOTS; + ctx->pos = cpos; +out: + __unlock_super(sb); +out_unlocked: + /* + * To improve performance, free namebuf after unlock sb_lock. + * If namebuf is not allocated, this function do nothing + */ + sdfat_free_namebuf(nb); + return err; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) */ +static inline sector_t __sdfat_bio_sector(struct bio *bio) +{ + return bio->bi_sector; +} + +static inline void __sdfat_set_bio_iterate(struct bio *bio, sector_t sector, + unsigned int size, unsigned int idx, unsigned int done) +{ + bio->bi_sector = sector; + bio->bi_idx = idx; + bio->bi_size = size; //PAGE_SIZE; +} + +static void __sdfat_truncate_pagecache(struct inode *inode, + loff_t to, loff_t newsize) +{ + truncate_pagecache(inode, to, newsize); +} + +static int sdfat_d_hash(const struct dentry *dentry, + const struct inode *inode, struct qstr *qstr) +{ + return __sdfat_d_hash(dentry, qstr); +} + +static int sdfat_d_hashi(const struct dentry *dentry, + const struct inode *inode, struct qstr *qstr) +{ + return __sdfat_d_hashi(dentry, qstr); +} + +static int sdfat_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + struct inode *inode = filp->f_path.dentry->d_inode; + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + DIR_ENTRY_T de; + DENTRY_NAMEBUF_T *nb = &(de.NameBuf); + unsigned long inum; + loff_t cpos; + int err = 0, fake_offset = 0; + + sdfat_init_namebuf(nb); + __lock_super(sb); + + cpos = filp->f_pos; + /* Fake . and .. for the root directory. */ + if ((fsi->vol_type == EXFAT) || (inode->i_ino == SDFAT_ROOT_INO)) { + while (cpos < ITER_POS_FILLED_DOTS) { + if (inode->i_ino == SDFAT_ROOT_INO) + inum = SDFAT_ROOT_INO; + else if (cpos == 0) + inum = inode->i_ino; + else /* (cpos == 1) */ + inum = parent_ino(filp->f_path.dentry); + + if (filldir(dirent, "..", cpos+1, cpos, inum, DT_DIR) < 0) + goto out; + cpos++; + filp->f_pos++; + } + if (cpos == ITER_POS_FILLED_DOTS) { + cpos = 0; + fake_offset = 1; + } + } + if (cpos & (DENTRY_SIZE - 1)) { + err = -ENOENT; + goto out; + } + + /* name buffer should be allocated before use */ + err = sdfat_alloc_namebuf(nb); + if (err) + goto out; +get_new: + SDFAT_I(inode)->fid.size = i_size_read(inode); + SDFAT_I(inode)->fid.rwoffset = cpos >> DENTRY_SIZE_BITS; + + if (cpos >= SDFAT_I(inode)->fid.size) + goto end_of_dir; + + err = fsapi_readdir(inode, &de); + if (err) { + // at least we tried to read a sector + // move cpos to next sector position (should be aligned) + if (err == -EIO) { + cpos += 1 << (sb->s_blocksize_bits); + cpos &= ~((u32)sb->s_blocksize-1); + } + + err = -EIO; + goto end_of_dir; + } + + cpos = SDFAT_I(inode)->fid.rwoffset << DENTRY_SIZE_BITS; + + if (!nb->lfn[0]) + goto end_of_dir; + + if (!memcmp(nb->sfn, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = inode->i_ino; + } else if (!memcmp(nb->sfn, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = parent_ino(filp->f_path.dentry); + } else { + loff_t i_pos = ((loff_t) SDFAT_I(inode)->fid.start_clu << 32) | + ((SDFAT_I(inode)->fid.rwoffset-1) & 0xffffffff); + struct inode *tmp = sdfat_iget(sb, i_pos); + + if (tmp) { + inum = tmp->i_ino; + iput(tmp); + } else { + inum = iunique(sb, SDFAT_ROOT_INO); + } + } + + /* Before calling dir_emit(), sb_lock should be released. + * Because page fault can occur in dir_emit() when the size of buffer given + * from user is larger than one page size + */ + __unlock_super(sb); + if (filldir(dirent, nb->lfn, strlen(nb->lfn), cpos, inum, + (de.Attr & ATTR_SUBDIR) ? DT_DIR : DT_REG) < 0) + goto out_unlocked; + __lock_super(sb); + + filp->f_pos = cpos; + goto get_new; + +end_of_dir: + if (!cpos && fake_offset) + cpos = ITER_POS_FILLED_DOTS; + filp->f_pos = cpos; +out: + __unlock_super(sb); +out_unlocked: + /* + * To improve performance, free namebuf after unlock sb_lock. + * If namebuf is not allocated, this function do nothing + */ + sdfat_free_namebuf(nb); + return err; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) + /* EMPTY */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0) */ +static inline struct inode *file_inode(const struct file *f) +{ + return f->f_dentry->d_inode; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) +static inline int __is_sb_dirty(struct super_block *sb) +{ + return SDFAT_SB(sb)->s_dirt; +} + +static inline void __set_sb_clean(struct super_block *sb) +{ + SDFAT_SB(sb)->s_dirt = 0; +} + +/* Workqueue wrapper for sdfat_write_super () */ +static void __write_super_delayed(struct work_struct *work) +{ + struct sdfat_sb_info *sbi; + struct super_block *sb; + + sbi = container_of(work, struct sdfat_sb_info, write_super_work.work); + sb = sbi->host_sb; + + /* XXX: Is this needed? */ + if (!sb || !down_read_trylock(&sb->s_umount)) { + DMSG("%s: skip delayed work(write_super).\n", __func__); + return; + } + + DMSG("%s: do delayed_work(write_super).\n", __func__); + + spin_lock(&sbi->work_lock); + sbi->write_super_queued = 0; + spin_unlock(&sbi->work_lock); + + sdfat_write_super(sb); + + up_read(&sb->s_umount); +} + +static void setup_sdfat_sync_super_wq(struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + mutex_init(&sbi->s_lock); + spin_lock_init(&sbi->work_lock); + INIT_DELAYED_WORK(&sbi->write_super_work, __write_super_delayed); + sbi->host_sb = sb; +} + +static inline bool __cancel_delayed_work_sync(struct sdfat_sb_info *sbi) +{ + return cancel_delayed_work_sync(&sbi->write_super_work); +} + +static inline void lock_super(struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + mutex_lock(&sbi->s_lock); +} + +static inline void unlock_super(struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + mutex_unlock(&sbi->s_lock); +} + +static int sdfat_revalidate(struct dentry *dentry, unsigned int flags) +{ + if (flags & LOOKUP_RCU) + return -ECHILD; + + return __sdfat_revalidate(dentry); +} + +static int sdfat_revalidate_ci(struct dentry *dentry, unsigned int flags) +{ + if (flags & LOOKUP_RCU) + return -ECHILD; + + return __sdfat_revalidate_ci(dentry, flags); +} + +static struct inode *sdfat_iget(struct super_block *sb, loff_t i_pos) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct sdfat_inode_info *info; + struct hlist_head *head = sbi->inode_hashtable + sdfat_hash(i_pos); + struct inode *inode = NULL; + + spin_lock(&sbi->inode_hash_lock); + hlist_for_each_entry(info, head, i_hash_fat) { + BUG_ON(info->vfs_inode.i_sb != sb); + + if (i_pos != info->i_pos) + continue; + inode = igrab(&info->vfs_inode); + if (inode) + break; + } + spin_unlock(&sbi->inode_hash_lock); + return inode; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) */ +static inline int __is_sb_dirty(struct super_block *sb) +{ + return sb->s_dirt; +} + +static inline void __set_sb_clean(struct super_block *sb) +{ + sb->s_dirt = 0; +} + +static void setup_sdfat_sync_super_wq(struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + sbi->host_sb = sb; +} + +static inline bool __cancel_delayed_work_sync(struct sdfat_sb_info *sbi) +{ + /* DO NOTHING */ + return 0; +} + +static inline void clear_inode(struct inode *inode) +{ + end_writeback(inode); +} + +static int sdfat_revalidate(struct dentry *dentry, struct nameidata *nd) +{ + if (nd && nd->flags & LOOKUP_RCU) + return -ECHILD; + + return __sdfat_revalidate(dentry); +} + +static int sdfat_revalidate_ci(struct dentry *dentry, struct nameidata *nd) +{ + if (nd && nd->flags & LOOKUP_RCU) + return -ECHILD; + + return __sdfat_revalidate_ci(dentry, nd ? nd->flags : 0); + +} + +static struct inode *sdfat_iget(struct super_block *sb, loff_t i_pos) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct sdfat_inode_info *info; + struct hlist_node *node; + struct hlist_head *head = sbi->inode_hashtable + sdfat_hash(i_pos); + struct inode *inode = NULL; + + spin_lock(&sbi->inode_hash_lock); + hlist_for_each_entry(info, node, head, i_hash_fat) { + BUG_ON(info->vfs_inode.i_sb != sb); + + if (i_pos != info->i_pos) + continue; + inode = igrab(&info->vfs_inode); + if (inode) + break; + } + spin_unlock(&sbi->inode_hash_lock); + return inode; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) +static struct dentry *sdfat_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + return __sdfat_lookup(dir, dentry); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0) */ +static struct dentry *sdfat_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) +{ + return __sdfat_lookup(dir, dentry); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + /* NOTHING NOW */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) */ +#define GLOBAL_ROOT_UID (0) +#define GLOBAL_ROOT_GID (0) + +static inline bool uid_eq(uid_t left, uid_t right) +{ + return left == right; +} + +static inline bool gid_eq(gid_t left, gid_t right) +{ + return left == right; +} + +static inline uid_t from_kuid_munged(struct user_namespace *to, uid_t kuid) +{ + return kuid; +} + +static inline gid_t from_kgid_munged(struct user_namespace *to, gid_t kgid) +{ + return kgid; +} + +static inline uid_t make_kuid(struct user_namespace *from, uid_t uid) +{ + return uid; +} + +static inline gid_t make_kgid(struct user_namespace *from, gid_t gid) +{ + return gid; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) +static struct dentry *__d_make_root(struct inode *root_inode) +{ + return d_make_root(root_inode); +} + +static void __sdfat_do_truncate(struct inode *inode, loff_t old, loff_t new) +{ + down_write(&SDFAT_I(inode)->truncate_lock); + truncate_setsize(inode, new); + sdfat_truncate(inode, old); + up_write(&SDFAT_I(inode)->truncate_lock); +} + +static sector_t sdfat_aop_bmap(struct address_space *mapping, sector_t block) +{ + sector_t blocknr; + + /* sdfat_get_cluster() assumes the requested blocknr isn't truncated. */ + down_read(&SDFAT_I(mapping->host)->truncate_lock); + blocknr = generic_block_bmap(mapping, block, sdfat_get_block); + up_read(&SDFAT_I(mapping->host)->truncate_lock); + return blocknr; +} + +static int sdfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + return __sdfat_mkdir(dir, dentry); +} + +static int sdfat_show_options(struct seq_file *m, struct dentry *root) +{ + return __sdfat_show_options(m, root->d_sb); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) */ +static inline void set_nlink(struct inode *inode, unsigned int nlink) +{ + inode->i_nlink = nlink; +} + +static struct dentry *__d_make_root(struct inode *root_inode) +{ + return d_alloc_root(root_inode); +} + +static void __sdfat_do_truncate(struct inode *inode, loff_t old, loff_t new) +{ + truncate_setsize(inode, new); + sdfat_truncate(inode, old); +} + +static sector_t sdfat_aop_bmap(struct address_space *mapping, sector_t block) +{ + sector_t blocknr; + + /* sdfat_get_cluster() assumes the requested blocknr isn't truncated. */ + down_read(&mapping->host->i_alloc_sem); + blocknr = generic_block_bmap(mapping, block, sdfat_get_block); + up_read(&mapping->host->i_alloc_sem); + return blocknr; +} + +static int sdfat_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + return __sdfat_mkdir(dir, dentry); +} + +static int sdfat_show_options(struct seq_file *m, struct vfsmount *mnt) +{ + return __sdfat_show_options(m, mnt->mnt_sb); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0) +#define __sdfat_generic_file_fsync(filp, start, end, datasync) \ + generic_file_fsync(filp, start, end, datasync) + +static int sdfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) +{ + return __sdfat_file_fsync(filp, start, end, datasync); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) */ +#define __sdfat_generic_file_fsync(filp, start, end, datasync) \ + generic_file_fsync(filp, datasync) +static int sdfat_file_fsync(struct file *filp, int datasync) +{ + return __sdfat_file_fsync(filp, 0, 0, datasync); +} +#endif + +/************************************************************************* + * MORE FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY + *************************************************************************/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) +static int sdfat_cmp(const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __sdfat_cmp(dentry, len, str, name); +} + +static int sdfat_cmpi(const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __sdfat_cmpi(dentry, len, str, name); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) +static int sdfat_cmp(const struct dentry *parent, const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __sdfat_cmp(dentry, len, str, name); +} + +static int sdfat_cmpi(const struct dentry *parent, const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __sdfat_cmpi(dentry, len, str, name); +} +#else +static int sdfat_cmp(const struct dentry *parent, const struct inode *pinode, + const struct dentry *dentry, const struct inode *inode, + unsigned int len, const char *str, const struct qstr *name) +{ + return __sdfat_cmp(dentry, len, str, name); +} + +static int sdfat_cmpi(const struct dentry *parent, const struct inode *pinode, + const struct dentry *dentry, const struct inode *inode, + unsigned int len, const char *str, const struct qstr *name) +{ + return __sdfat_cmpi(dentry, len, str, name); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) +static const char *sdfat_follow_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done) +{ + struct sdfat_inode_info *ei = SDFAT_I(inode); + + return (char *)(ei->target); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) +static const char *sdfat_follow_link(struct dentry *dentry, void **cookie) +{ + struct sdfat_inode_info *ei = SDFAT_I(dentry->d_inode); + + return *cookie = (char *)(ei->target); +} +#else +static void *sdfat_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + struct sdfat_inode_info *ei = SDFAT_I(dentry->d_inode); + + nd_set_link(nd, (char *)(ei->target)); + return NULL; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) +static ssize_t sdfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_iter_count(iter); + int rw = iov_iter_rw(iter); + loff_t offset = iocb->ki_pos; + + return __sdfat_direct_IO(rw, iocb, inode, + (void *)iter, offset, count, 0 /* UNUSED */); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +static ssize_t sdfat_direct_IO(struct kiocb *iocb, + struct iov_iter *iter, + loff_t offset) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_iter_count(iter); + int rw = iov_iter_rw(iter); + + return __sdfat_direct_IO(rw, iocb, inode, + (void *)iter, offset, count, 0 /* UNUSED */); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) +static ssize_t sdfat_direct_IO(int rw, struct kiocb *iocb, + struct iov_iter *iter, + loff_t offset) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_iter_count(iter); + + return __sdfat_direct_IO(rw, iocb, inode, + (void *)iter, offset, count, 0 /* UNUSED */); +} +#else +static ssize_t sdfat_direct_IO(int rw, struct kiocb *iocb, + const struct iovec *iov, loff_t offset, unsigned long nr_segs) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_length(iov, nr_segs); + + return __sdfat_direct_IO(rw, iocb, inode, + (void *)iov, offset, count, nr_segs); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) +static inline ssize_t __sdfat_blkdev_direct_IO(int unused, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t unused_1, + unsigned long nr_segs) +{ + struct iov_iter *iter = (struct iov_iter *)iov_u; + + return blockdev_direct_IO(iocb, inode, iter, sdfat_get_block); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +static inline ssize_t __sdfat_blkdev_direct_IO(int unused, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + struct iov_iter *iter = (struct iov_iter *)iov_u; + + return blockdev_direct_IO(iocb, inode, iter, offset, sdfat_get_block); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) +static inline ssize_t __sdfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + struct iov_iter *iter = (struct iov_iter *)iov_u; + + return blockdev_direct_IO(rw, iocb, inode, iter, + offset, sdfat_get_block); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) +static inline ssize_t __sdfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + const struct iovec *iov = (const struct iovec *)iov_u; + + return blockdev_direct_IO(rw, iocb, inode, iov, + offset, nr_segs, sdfat_get_block); +} +#else +static inline ssize_t __sdfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + const struct iovec *iov = (const struct iovec *)iov_u; + + return blockdev_direct_IO(rw, iocb, inode, inode->i_sb->s_bdev, iov, + offset, nr_segs, sdfat_get_block, NULL); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) +static int sdfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, + bool excl) +{ + return __sdfat_create(dir, dentry); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) +static int sdfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, + struct nameidata *nd) +{ + return __sdfat_create(dir, dentry); +} +#else +static int sdfat_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd) +{ + return __sdfat_create(dir, dentry); +} +#endif + + +/************************************************************************* + * WRAP FUNCTIONS FOR DEBUGGING + *************************************************************************/ +#ifdef CONFIG_SDFAT_TRACE_SB_LOCK +static inline void __lock_super(struct super_block *sb) +{ + lock_super(sb); + __lock_jiffies = jiffies; +} + +static inline void __unlock_super(struct super_block *sb) +{ + int time = ((jiffies - __lock_jiffies) * 1000 / HZ); + /* FIXME : error message should be modified */ + if (time > 10) + EMSG("lock_super in %s (%d ms)\n", __func__, time); + + unlock_super(sb); +} +#else /* CONFIG_SDFAT_TRACE_SB_LOCK */ +static inline void __lock_super(struct super_block *sb) +{ + lock_super(sb); +} + +static inline void __unlock_super(struct super_block *sb) +{ + unlock_super(sb); +} +#endif /* CONFIG_SDFAT_TRACE_SB_LOCK */ + +/************************************************************************* + * NORMAL FUNCTIONS + *************************************************************************/ +static inline loff_t sdfat_make_i_pos(FILE_ID_T *fid) +{ + return ((loff_t) fid->dir.dir << 32) | (fid->entry & 0xffffffff); +} + +/*======================================================================*/ +/* Directory Entry Name Buffer Operations */ +/*======================================================================*/ +static void sdfat_init_namebuf(DENTRY_NAMEBUF_T *nb) +{ + nb->lfn = NULL; + nb->sfn = NULL; + nb->lfnbuf_len = 0; + nb->sfnbuf_len = 0; +} + +static int sdfat_alloc_namebuf(DENTRY_NAMEBUF_T *nb) +{ + nb->lfn = __getname(); + if (!nb->lfn) + return -ENOMEM; + nb->sfn = nb->lfn + MAX_VFSNAME_BUF_SIZE; + nb->lfnbuf_len = MAX_VFSNAME_BUF_SIZE; + nb->sfnbuf_len = MAX_VFSNAME_BUF_SIZE; + return 0; +} + +static void sdfat_free_namebuf(DENTRY_NAMEBUF_T *nb) +{ + if (!nb->lfn) + return; + + __putname(nb->lfn); + sdfat_init_namebuf(nb); +} + +/*======================================================================*/ +/* Directory Entry Operations */ +/*======================================================================*/ +#define SDFAT_DSTATE_LOCKED (void *)(0xCAFE2016) +#define SDFAT_DSTATE_UNLOCKED (void *)(0x00000000) + +static inline void __lock_d_revalidate(struct dentry *dentry) +{ + spin_lock(&dentry->d_lock); + dentry->d_fsdata = SDFAT_DSTATE_LOCKED; + spin_unlock(&dentry->d_lock); +} + +static inline void __unlock_d_revalidate(struct dentry *dentry) +{ + spin_lock(&dentry->d_lock); + dentry->d_fsdata = SDFAT_DSTATE_UNLOCKED; + spin_unlock(&dentry->d_lock); +} + +/* __check_dstate_locked requires dentry->d_lock */ +static inline int __check_dstate_locked(struct dentry *dentry) +{ + if (dentry->d_fsdata == SDFAT_DSTATE_LOCKED) + return 1; + + return 0; +} + +/* + * If new entry was created in the parent, it could create the 8.3 + * alias (the shortname of logname). So, the parent may have the + * negative-dentry which matches the created 8.3 alias. + * + * If it happened, the negative dentry isn't actually negative + * anymore. So, drop it. + */ +static int __sdfat_revalidate_common(struct dentry *dentry) +{ + int ret = 1; + + spin_lock(&dentry->d_lock); + if ((!dentry->d_inode) && (!__check_dstate_locked(dentry) && + (dentry->d_time != dentry->d_parent->d_inode->i_version))) { + ret = 0; + } + spin_unlock(&dentry->d_lock); + return ret; +} + +static int __sdfat_revalidate(struct dentry *dentry) +{ + /* This is not negative dentry. Always valid. */ + if (dentry->d_inode) + return 1; + return __sdfat_revalidate_common(dentry); +} + +static int __sdfat_revalidate_ci(struct dentry *dentry, unsigned int flags) +{ + /* + * This is not negative dentry. Always valid. + * + * Note, rename() to existing directory entry will have ->d_inode, + * and will use existing name which isn't specified name by user. + * + * We may be able to drop this positive dentry here. But dropping + * positive dentry isn't good idea. So it's unsupported like + * rename("filename", "FILENAME") for now. + */ + if (dentry->d_inode) + return 1; +#if 0 /* Blocked below code for lookup_one_len() called by stackable FS */ + /* + * This may be nfsd (or something), anyway, we can't see the + * intent of this. So, since this can be for creation, drop it. + */ + if (!flags) + return 0; +#endif + /* + * Drop the negative dentry, in order to make sure to use the + * case sensitive name which is specified by user if this is + * for creation. + */ + if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET)) + return 0; + return __sdfat_revalidate_common(dentry); +} + + +/* returns the length of a struct qstr, ignoring trailing dots */ +static unsigned int __sdfat_striptail_len(unsigned int len, const char *name) +{ + while (len && name[len - 1] == '.') + len--; + return len; +} + +static unsigned int sdfat_striptail_len(const struct qstr *qstr) +{ + return __sdfat_striptail_len(qstr->len, qstr->name); +} + +/* + * Compute the hash for the sdfat name corresponding to the dentry. + * Note: if the name is invalid, we leave the hash code unchanged so + * that the existing dentry can be used. The sdfat fs routines will + * return ENOENT or EINVAL as appropriate. + */ +static int __sdfat_d_hash(const struct dentry *dentry, struct qstr *qstr) +{ + unsigned int len = sdfat_striptail_len(qstr); + + qstr->hash = __sdfat_full_name_hash(dentry, qstr->name, len); + return 0; +} + +/* + * Compute the hash for the sdfat name corresponding to the dentry. + * Note: if the name is invalid, we leave the hash code unchanged so + * that the existing dentry can be used. The sdfat fs routines will + * return ENOENT or EINVAL as appropriate. + */ +static int __sdfat_d_hashi(const struct dentry *dentry, struct qstr *qstr) +{ + struct nls_table *t = SDFAT_SB(dentry->d_sb)->nls_io; + const unsigned char *name; + unsigned int len; + unsigned long hash; + + name = qstr->name; + len = sdfat_striptail_len(qstr); + + hash = __sdfat_init_name_hash(dentry); + while (len--) + hash = partial_name_hash(nls_tolower(t, *name++), hash); + qstr->hash = end_name_hash(hash); + + return 0; +} + +/* + * Case sensitive compare of two sdfat names. + */ +static int __sdfat_cmp(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name) +{ + unsigned int alen, blen; + + /* A filename cannot end in '.' or we treat it like it has none */ + alen = sdfat_striptail_len(name); + blen = __sdfat_striptail_len(len, str); + if (alen == blen) { + if (strncmp(name->name, str, alen) == 0) + return 0; + } + return 1; +} + +/* + * Case insensitive compare of two sdfat names. + */ +static int __sdfat_cmpi(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name) +{ + struct nls_table *t = SDFAT_SB(dentry->d_sb)->nls_io; + unsigned int alen, blen; + + /* A filename cannot end in '.' or we treat it like it has none */ + alen = sdfat_striptail_len(name); + blen = __sdfat_striptail_len(len, str); + if (alen == blen) { + if (nls_strnicmp(t, name->name, str, alen) == 0) + return 0; + } + return 1; +} + +static const struct dentry_operations sdfat_dentry_ops = { + .d_revalidate = sdfat_revalidate, + .d_hash = sdfat_d_hash, + .d_compare = sdfat_cmp, +}; + +static const struct dentry_operations sdfat_ci_dentry_ops = { + .d_revalidate = sdfat_revalidate_ci, + .d_hash = sdfat_d_hashi, + .d_compare = sdfat_cmpi, +}; + +#ifdef CONFIG_SDFAT_DFR +/*----------------------------------------------------------------------*/ +/* Defragmentation related */ +/*----------------------------------------------------------------------*/ +/** + * @fn defrag_cleanup_reqs + * @brief clean-up defrag info depending on error flag + * @return void + * @param sb super block + * @param error error flag + */ +static void defrag_cleanup_reqs(INOUT struct super_block *sb, IN int error) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct defrag_info *sb_dfr = &(sbi->dfr_info); + struct defrag_info *ino_dfr = NULL, *tmp = NULL; + /* sdfat patch 0.96 : sbi->dfr_info crash problem */ + __lock_super(sb); + + /* Clean-up ino_dfr */ + if (!error) { + list_for_each_entry_safe(ino_dfr, tmp, &sb_dfr->entry, entry) { + struct inode *inode = &(container_of(ino_dfr, struct sdfat_inode_info, dfr_info)->vfs_inode); + + mutex_lock(&ino_dfr->lock); + + atomic_set(&ino_dfr->stat, DFR_INO_STAT_IDLE); + + list_del(&ino_dfr->entry); + + ino_dfr->chunks = NULL; + ino_dfr->nr_chunks = 0; + INIT_LIST_HEAD(&ino_dfr->entry); + + BUG_ON(!mutex_is_locked(&ino_dfr->lock)); + mutex_unlock(&ino_dfr->lock); + + iput(inode); + } + } + + /* Clean-up sb_dfr */ + sb_dfr->chunks = NULL; + sb_dfr->nr_chunks = 0; + INIT_LIST_HEAD(&sb_dfr->entry); + + /* Clear dfr_new_clus page */ + memset(sbi->dfr_new_clus, 0, PAGE_SIZE); + sbi->dfr_new_idx = 1; + memset(sbi->dfr_page_wb, 0, PAGE_SIZE); + + sbi->dfr_hint_clus = sbi->dfr_hint_idx = sbi->dfr_reserved_clus = 0; + + __unlock_super(sb); +} + +/** + * @fn defrag_validate_pages + * @brief validate and mark dirty for victiim pages + * @return 0 on success, -errno otherwise + * @param inode inode + * @param chunk given chunk + * @remark protected by inode_lock and super_lock + */ +static int +defrag_validate_pages( + IN struct inode *inode, + IN struct defrag_chunk_info *chunk) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct page *page = NULL; + unsigned int i_size = 0, page_off = 0, page_nr = 0; + int buf_i = 0, i = 0, err = 0; + + i_size = i_size_read(inode); + page_off = chunk->f_clus * PAGES_PER_CLUS(sb); + page_nr = (i_size / PAGE_SIZE) + ((i_size % PAGE_SIZE) ? 1 : 0); + if ((i_size <= 0) || (page_nr <= 0)) { + dfr_err("inode %p, i_size %d, page_nr %d", inode, i_size, page_nr); + return -EINVAL; + } + + /* Get victim pages + * and check its dirty/writeback/mapped state + */ + for (i = 0; + i < min((int)(page_nr - page_off), (int)(chunk->nr_clus * PAGES_PER_CLUS(sb))); + i++) { + page = find_get_page(inode->i_mapping, page_off + i); + if (page) + if (!trylock_page(page)) { + put_page(page); + page = NULL; + } + + if (!page) { + dfr_debug("get/lock_page() failed, index %d", i); + err = -EINVAL; + goto error; + } + + sbi->dfr_pagep[buf_i++] = page; + if (PageError(page) || !PageUptodate(page) || PageDirty(page) || + PageWriteback(page) || page_mapped(page)) { + dfr_debug("page %p, err %d, uptodate %d, " + "dirty %d, wb %d, mapped %d", + page, PageError(page), PageUptodate(page), + PageDirty(page), PageWriteback(page), + page_mapped(page)); + err = -EINVAL; + goto error; + } + + set_bit((page->index & (PAGES_PER_CLUS(sb) - 1)), + (volatile unsigned long *)&(sbi->dfr_page_wb[chunk->new_idx + i / PAGES_PER_CLUS(sb)])); + + page = NULL; + } + + /** + * All pages in the chunks are valid. + */ + i_size -= (chunk->f_clus * (sbi->fsi.cluster_size)); + BUG_ON(((i_size / PAGE_SIZE) + ((i_size % PAGE_SIZE) ? 1 : 0)) != (page_nr - page_off)); + + for (i = 0; i < buf_i; i++) { + struct buffer_head *bh = NULL, *head = NULL; + int bh_idx = 0; + + page = sbi->dfr_pagep[i]; + BUG_ON(!page); + + /* Mark dirty in page */ + set_page_dirty(page); + mark_page_accessed(page); + + /* Attach empty BHs */ + if (!page_has_buffers(page)) + create_empty_buffers(page, 1 << inode->i_blkbits, 0); + + /* Mark dirty in BHs */ + bh = head = page_buffers(page); + BUG_ON(!bh && !i_size); + do { + if ((bh_idx >= 1) && (bh_idx >= (i_size >> inode->i_blkbits))) { + clear_buffer_dirty(bh); + } else { + if (PageUptodate(page)) + if (!buffer_uptodate(bh)) + set_buffer_uptodate(bh); + + /* Set this bh as delay */ + set_buffer_new(bh); + set_buffer_delay(bh); + + mark_buffer_dirty(bh); + } + + bh_idx++; + bh = bh->b_this_page; + } while (bh != head); + + /* Mark this page accessed */ + mark_page_accessed(page); + + i_size -= PAGE_SIZE; + } + +error: + /* Unlock and put refs for pages */ + for (i = 0; i < buf_i; i++) { + BUG_ON(!sbi->dfr_pagep[i]); + unlock_page(sbi->dfr_pagep[i]); + put_page(sbi->dfr_pagep[i]); + } + memset(sbi->dfr_pagep, 0, sizeof(PAGE_SIZE)); + + return err; +} + + +/** + * @fn defrag_validate_reqs + * @brief validate defrag requests + * @return negative if all requests not valid, 0 otherwise + * @param sb super block + * @param chunks given chunks + */ +static int +defrag_validate_reqs( + IN struct super_block *sb, + INOUT struct defrag_chunk_info *chunks) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct defrag_info *sb_dfr = &(sbi->dfr_info); + int i = 0, err = 0, err_cnt = 0; + + /* Validate all reqs */ + for (i = REQ_HEADER_IDX + 1; i < sb_dfr->nr_chunks; i++) { + struct defrag_chunk_info *chunk = NULL; + struct inode *inode = NULL; + struct defrag_info *ino_dfr = NULL; + + chunk = &chunks[i]; + + /* Check inode */ + __lock_super(sb); + inode = sdfat_iget(sb, chunk->i_pos); + if (!inode) { + dfr_debug("inode not found, i_pos %08llx", chunk->i_pos); + chunk->stat = DFR_CHUNK_STAT_ERR; + err_cnt++; + __unlock_super(sb); + continue; + } + __unlock_super(sb); + + dfr_debug("req[%d] inode %p, i_pos %08llx, f_clus %d, " + "d_clus %08x, nr %d, prev %08x, next %08x", + i, inode, chunk->i_pos, chunk->f_clus, chunk->d_clus, + chunk->nr_clus, chunk->prev_clus, chunk->next_clus); + /** + * Lock ordering: inode_lock -> lock_super + */ + inode_lock(inode); + __lock_super(sb); + + /* Check if enough buffers exist for chunk->new_idx */ + if ((sbi->dfr_new_idx + chunk->nr_clus) >= (PAGE_SIZE / sizeof(int))) { + dfr_err("dfr_new_idx %d, chunk->nr_clus %d", + sbi->dfr_new_idx, chunk->nr_clus); + err = -ENOSPC; + goto unlock; + } + + /* Reserve clusters for defrag with DA */ + err = fsapi_dfr_reserve_clus(sb, chunk->nr_clus); + if (err) + goto unlock; + + /* Check clusters */ + err = fsapi_dfr_validate_clus(inode, chunk, 0); + if (err) { + fsapi_dfr_reserve_clus(sb, 0 - chunk->nr_clus); + dfr_debug("Cluster validation: err %d", err); + goto unlock; + } + + /* Check pages */ + err = defrag_validate_pages(inode, chunk); + if (err) { + fsapi_dfr_reserve_clus(sb, 0 - chunk->nr_clus); + dfr_debug("Page validation: err %d", err); + goto unlock; + } + + /* Mark IGNORE flag to victim AU */ + if (sbi->options.improved_allocation & SDFAT_ALLOC_SMART) + fsapi_dfr_mark_ignore(sb, chunk->d_clus); + + ino_dfr = &(SDFAT_I(inode)->dfr_info); + mutex_lock(&ino_dfr->lock); + + /* Update chunk info */ + chunk->stat = DFR_CHUNK_STAT_REQ; + chunk->new_idx = sbi->dfr_new_idx; + + /* Update ino_dfr info */ + if (list_empty(&(ino_dfr->entry))) { + list_add_tail(&ino_dfr->entry, &sb_dfr->entry); + ino_dfr->chunks = chunk; + igrab(inode); + } + ino_dfr->nr_chunks++; + + atomic_set(&ino_dfr->stat, DFR_INO_STAT_REQ); + + BUG_ON(!mutex_is_locked(&ino_dfr->lock)); + mutex_unlock(&ino_dfr->lock); + + /* Reserved buffers for chunk->new_idx */ + sbi->dfr_new_idx += chunk->nr_clus; + +unlock: + if (err) { + chunk->stat = DFR_CHUNK_STAT_ERR; + err_cnt++; + } + iput(inode); + __unlock_super(sb); + inode_unlock(inode); + } + + /* Return error if all chunks are invalid */ + if (err_cnt == sb_dfr->nr_chunks - 1) { + dfr_debug("%s failed (err_cnt %d)", __func__, err_cnt); + return -ENXIO; + } + + return 0; +} + + +/** + * @fn defrag_check_fs_busy + * @brief check if this module busy + * @return 0 when idle, 1 otherwise + * @param sb super block + * @param reserved_clus # of reserved clusters + * @param queued_pages # of queued pages + */ +static int +defrag_check_fs_busy( + IN struct super_block *sb, + OUT int *reserved_clus, + OUT int *queued_pages) +{ + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + int err = 0; + + *reserved_clus = *queued_pages = 0; + + __lock_super(sb); + *reserved_clus = fsi->reserved_clusters; + *queued_pages = atomic_read(&SDFAT_SB(sb)->stat_n_pages_queued); + + if (*reserved_clus || *queued_pages) + err = 1; + __unlock_super(sb); + + return err; +} + + +/** + * @fn sdfat_ioctl_defrag_req + * @brief ioctl to send defrag requests + * @return 0 on success, -errno otherwise + * @param inode inode + * @param uarg given requests + */ +static int +sdfat_ioctl_defrag_req( + IN struct inode *inode, + INOUT unsigned int *uarg) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct defrag_info *sb_dfr = &(sbi->dfr_info); + struct defrag_chunk_header head; + struct defrag_chunk_info *chunks = NULL; + unsigned int len = 0; + int err = 0; + unsigned long timeout = 0; + + /* Check overlapped defrag */ + if (atomic_cmpxchg(&sb_dfr->stat, DFR_SB_STAT_IDLE, DFR_SB_STAT_REQ)) { + dfr_debug("sb_dfr->stat %d", atomic_read(&sb_dfr->stat)); + return -EBUSY; + } + + /* Check if defrag required */ + __lock_super(sb); + if (!fsapi_dfr_check_dfr_required(sb, NULL, NULL, NULL)) { + dfr_debug("Not enough space left for defrag (err %d)", -ENOSPC); + atomic_set(&sb_dfr->stat, DFR_SB_STAT_IDLE); + __unlock_super(sb); + return -ENOSPC; + } + __unlock_super(sb); + + /* Copy args */ + memset(&head, 0, sizeof(struct defrag_chunk_header)); + err = copy_from_user(&head, uarg, sizeof(struct defrag_chunk_info)); + ERR_HANDLE(err); + + /* If FS busy, cancel defrag */ + if (!(head.mode == DFR_MODE_TEST)) { + int reserved_clus = 0, queued_pages = 0; + + err = defrag_check_fs_busy(sb, &reserved_clus, &queued_pages); + if (err) { + dfr_debug("FS busy, cancel defrag (reserved_clus %d, queued_pages %d)", + reserved_clus, queued_pages); + err = -EBUSY; + goto error; + } + } + + /* Total length is saved in the chunk header's nr_chunks field */ + len = head.nr_chunks; + ERR_HANDLE2(!len, err, -EINVAL); + + dfr_debug("IOC_DFR_REQ started (mode %d, nr_req %d)", head.mode, len - 1); + if (get_order(len * sizeof(struct defrag_chunk_info)) > MAX_ORDER) { + dfr_debug("len %d, sizeof(struct defrag_chunk_info) %lu, MAX_ORDER %d", + len, sizeof(struct defrag_chunk_info), MAX_ORDER); + err = -EINVAL; + goto error; + } + chunks = alloc_pages_exact(len * sizeof(struct defrag_chunk_info), + GFP_KERNEL | __GFP_ZERO); + ERR_HANDLE2(!chunks, err, -ENOMEM) + + err = copy_from_user(chunks, uarg, len * sizeof(struct defrag_chunk_info)); + ERR_HANDLE(err); + + /* Initialize sb_dfr */ + sb_dfr->chunks = chunks; + sb_dfr->nr_chunks = len; + + /* Validate reqs & mark defrag/dirty */ + err = defrag_validate_reqs(sb, sb_dfr->chunks); + ERR_HANDLE(err); + + atomic_set(&sb_dfr->stat, DFR_SB_STAT_VALID); + + /* Wait for defrag completion */ + if (head.mode == DFR_MODE_ONESHOT) + timeout = 0; + else if (head.mode & DFR_MODE_BACKGROUND) + timeout = DFR_DEFAULT_TIMEOUT; + else + timeout = DFR_MIN_TIMEOUT; + + dfr_debug("Wait for completion (timeout %ld)", timeout); + init_completion(&sbi->dfr_complete); + timeout = wait_for_completion_timeout(&sbi->dfr_complete, timeout); + + if (!timeout) { + /* Force defrag_updat_fat() after timeout. */ + dfr_debug("Force sync(), mode %d, left-timeout %ld", head.mode, timeout); + + down_read(&sb->s_umount); + + sync_inodes_sb(sb); + + __lock_super(sb); + fsapi_dfr_update_fat_next(sb); + + fsapi_sync_fs(sb, 1); + +#ifdef CONFIG_SDFAT_DFR_DEBUG + /* SPO test */ + fsapi_dfr_spo_test(sb, DFR_SPO_FAT_NEXT, __func__); +#endif + + fsapi_dfr_update_fat_prev(sb, 1); + fsapi_sync_fs(sb, 1); + + __unlock_super(sb); + + up_read(&sb->s_umount); + } + +#ifdef CONFIG_SDFAT_DFR_DEBUG + /* SPO test */ + fsapi_dfr_spo_test(sb, DFR_SPO_NORMAL, __func__); +#endif + + __lock_super(sb); + /* Send DISCARD to clean-ed AUs */ + fsapi_dfr_check_discard(sb); + +#ifdef CONFIG_SDFAT_DFR_DEBUG + /* SPO test */ + fsapi_dfr_spo_test(sb, DFR_SPO_DISCARD, __func__); +#endif + + /* Unmark IGNORE flag to all victim AUs */ + fsapi_dfr_unmark_ignore_all(sb); + __unlock_super(sb); + + err = copy_to_user(uarg, sb_dfr->chunks, sizeof(struct defrag_chunk_info) * len); + ERR_HANDLE(err); + +error: + /* Clean-up sb_dfr & ino_dfr */ + defrag_cleanup_reqs(sb, err); + + if (chunks) + free_pages_exact(chunks, len * sizeof(struct defrag_chunk_info)); + + /* Set sb_dfr's state as IDLE */ + atomic_set(&sb_dfr->stat, DFR_SB_STAT_IDLE); + + dfr_debug("IOC_DFR_REQ done (err %d)", err); + return err; +} + +/** + * @fn sdfat_ioctl_defrag_trav + * @brief ioctl to traverse given directory for defrag + * @return 0 on success, -errno otherwise + * @param inode inode + * @param uarg output buffer + */ +static int +sdfat_ioctl_defrag_trav( + IN struct inode *inode, + INOUT unsigned int *uarg) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct defrag_info *sb_dfr = &(sbi->dfr_info); + struct defrag_trav_arg *args = (struct defrag_trav_arg *) sbi->dfr_pagep; + struct defrag_trav_header *header = (struct defrag_trav_header *) args; + int err = 0; + + /* Check overlapped defrag */ + if (atomic_cmpxchg(&sb_dfr->stat, DFR_SB_STAT_IDLE, DFR_SB_STAT_REQ)) { + dfr_debug("sb_dfr->stat %d", atomic_read(&sb_dfr->stat)); + return -EBUSY; + } + + /* Check if defrag required */ + __lock_super(sb); + if (!fsapi_dfr_check_dfr_required(sb, NULL, NULL, NULL)) { + dfr_debug("Not enough space left for defrag (err %d)", -ENOSPC); + atomic_set(&sb_dfr->stat, DFR_SB_STAT_IDLE); + __unlock_super(sb); + return -ENOSPC; + } + __unlock_super(sb); + + /* Copy args */ + err = copy_from_user(args, uarg, PAGE_SIZE); + ERR_HANDLE(err); + + /** + * Check args. + * ROOT directory has i_pos = 0 and start_clus = 0 . + */ + if (!(header->type & DFR_TRAV_TYPE_HEADER)) { + err = -EINVAL; + dfr_debug("type %d, i_pos %08llx, start_clus %08x", + header->type, header->i_pos, header->start_clus); + goto error; + } + + /* If FS busy, cancel defrag */ + if (!(header->type & DFR_TRAV_TYPE_TEST)) { + unsigned int reserved_clus = 0, queued_pages = 0; + + err = defrag_check_fs_busy(sb, &reserved_clus, &queued_pages); + if (err) { + dfr_debug("FS busy, cancel defrag (reserved_clus %d, queued_pages %d)", + reserved_clus, queued_pages); + err = -EBUSY; + goto error; + } + } + + /* Scan given directory and gather info */ + inode_lock(inode); + __lock_super(sb); + err = fsapi_dfr_scan_dir(sb, (void *)args); + __unlock_super(sb); + inode_unlock(inode); + ERR_HANDLE(err); + + /* Copy the result to user */ + err = copy_to_user(uarg, args, PAGE_SIZE); + ERR_HANDLE(err); + +error: + memset(sbi->dfr_pagep, 0, PAGE_SIZE); + + atomic_set(&sb_dfr->stat, DFR_SB_STAT_IDLE); + return err; +} + +/** + * @fn sdfat_ioctl_defrag_info + * @brief ioctl to get HW param info + * @return 0 on success, -errno otherwise + * @param sb super block + * @param uarg output buffer + */ +static int +sdfat_ioctl_defrag_info( + IN struct super_block *sb, + OUT unsigned int *uarg) +{ + struct defrag_info_arg info_arg; + int err = 0; + + memset(&info_arg, 0, sizeof(struct defrag_info_arg)); + + __lock_super(sb); + err = fsapi_dfr_get_info(sb, &info_arg); + __unlock_super(sb); + ERR_HANDLE(err); + dfr_debug("IOC_DFR_INFO: sec_per_au %d, hidden_sectors %d", + info_arg.sec_per_au, info_arg.hidden_sectors); + + err = copy_to_user(uarg, &info_arg, sizeof(struct defrag_info_arg)); +error: + return err; +} + +#endif /* CONFIG_SDFAT_DFR */ + +static inline int __do_dfr_map_cluster(struct inode *inode, u32 clu_offset, unsigned int *clus_ptr) +{ +#ifdef CONFIG_SDFAT_DFR + return fsapi_dfr_map_clus(inode, clu_offset, clus_ptr); +#else + return 0; +#endif +} + +static inline int __check_dfr_on(struct inode *inode, loff_t start, loff_t end, const char *fname) +{ +#ifdef CONFIG_SDFAT_DFR + struct defrag_info *ino_dfr = &(SDFAT_I(inode)->dfr_info); + + if ((atomic_read(&ino_dfr->stat) == DFR_INO_STAT_REQ) && + fsapi_dfr_check_dfr_on(inode, start, end, 0, fname)) + return 1; +#endif + return 0; +} + +static inline int __cancel_dfr_work(struct inode *inode, loff_t start, loff_t end, const char *fname) +{ +#ifdef CONFIG_SDFAT_DFR + struct defrag_info *ino_dfr = &(SDFAT_I(inode)->dfr_info); + /* Cancel DEFRAG */ + if (atomic_read(&ino_dfr->stat) == DFR_INO_STAT_REQ) + fsapi_dfr_check_dfr_on(inode, start, end, 1, fname); +#endif + return 0; +} + +static inline int __dfr_writepage_end_io(struct page *page) +{ +#ifdef CONFIG_SDFAT_DFR + struct defrag_info *ino_dfr = &(SDFAT_I(page->mapping->host)->dfr_info); + + if (atomic_read(&ino_dfr->stat) == DFR_INO_STAT_REQ) + fsapi_dfr_writepage_endio(page); +#endif + return 0; +} + +static inline void __init_dfr_info(struct inode *inode) +{ +#ifdef CONFIG_SDFAT_DFR + memset(&(SDFAT_I(inode)->dfr_info), 0, sizeof(struct defrag_info)); + INIT_LIST_HEAD(&(SDFAT_I(inode)->dfr_info.entry)); + mutex_init(&(SDFAT_I(inode)->dfr_info.lock)); +#endif +} + +static inline int __alloc_dfr_mem_if_required(struct super_block *sb) +{ +#ifdef CONFIG_SDFAT_DFR + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + if (!sbi->options.defrag) + return 0; + + memset(&sbi->dfr_info, 0, sizeof(struct defrag_info)); + INIT_LIST_HEAD(&(sbi->dfr_info.entry)); + mutex_init(&(sbi->dfr_info.lock)); + + sbi->dfr_new_clus = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!sbi->dfr_new_clus) { + dfr_debug("error %d", -ENOMEM); + return -ENOMEM; + } + sbi->dfr_new_idx = 1; + + sbi->dfr_page_wb = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!sbi->dfr_page_wb) { + dfr_debug("error %d", -ENOMEM); + return -ENOMEM; + } + + sbi->dfr_pagep = alloc_pages_exact(sizeof(struct page *) * + PAGES_PER_AU(sb), GFP_KERNEL | __GFP_ZERO); + if (!sbi->dfr_pagep) { + dfr_debug("error %d", -ENOMEM); + return -ENOMEM; + } +#endif + return 0; +} + +static void __free_dfr_mem_if_required(struct super_block *sb) +{ +#ifdef CONFIG_SDFAT_DFR + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + if (sbi->dfr_pagep) { + free_pages_exact(sbi->dfr_pagep, sizeof(struct page *) * PAGES_PER_AU(sb)); + sbi->dfr_pagep = NULL; + } + + /* thanks for kfree */ + kfree(sbi->dfr_page_wb); + sbi->dfr_page_wb = NULL; + + kfree(sbi->dfr_new_clus); + sbi->dfr_new_clus = NULL; +#endif +} + + +static int sdfat_file_mmap(struct file *file, struct vm_area_struct *vm_struct) +{ + __cancel_dfr_work(file->f_mapping->host, + (loff_t)vm_struct->vm_start, + (loff_t)(vm_struct->vm_end - 1), + __func__); + + return generic_file_mmap(file, vm_struct); +} + +static int sdfat_ioctl_volume_id(struct inode *dir) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(dir->i_sb); + FS_INFO_T *fsi = &(sbi->fsi); + + return fsi->vol_id; +} + +static int sdfat_dfr_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ +#ifdef CONFIG_SDFAT_DFR + switch (cmd) { + case SDFAT_IOCTL_DFR_INFO: { + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &SDFAT_SB(sb)->fsi; + unsigned int __user *uarg = (unsigned int __user *) arg; + + __lock_super(sb); + /* Check FS type (FAT32 only) */ + if (fsi->vol_type != FAT32) { + dfr_err("Defrag not supported, vol_type %d", fsi->vol_type); + __unlock_super(sb); + return -EPERM; + + } + + /* Check if SB's defrag option enabled */ + if (!(SDFAT_SB(sb)->options.defrag)) { + dfr_err("Defrag not supported, sbi->options.defrag %d", SDFAT_SB(sb)->options.defrag); + __unlock_super(sb); + return -EPERM; + } + + /* Only IOCTL on mount-point allowed */ + if (filp->f_path.mnt->mnt_root != filp->f_path.dentry) { + dfr_err("IOC_DFR_INFO only allowed on ROOT, root %p, dentry %p", + filp->f_path.mnt->mnt_root, filp->f_path.dentry); + __unlock_super(sb); + return -EPERM; + } + __unlock_super(sb); + + return sdfat_ioctl_defrag_info(sb, uarg); + } + case SDFAT_IOCTL_DFR_TRAV: { + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &SDFAT_SB(sb)->fsi; + unsigned int __user *uarg = (unsigned int __user *) arg; + + __lock_super(sb); + /* Check FS type (FAT32 only) */ + if (fsi->vol_type != FAT32) { + dfr_err("Defrag not supported, vol_type %d", fsi->vol_type); + __unlock_super(sb); + return -EPERM; + + } + + /* Check if SB's defrag option enabled */ + if (!(SDFAT_SB(sb)->options.defrag)) { + dfr_err("Defrag not supported, sbi->options.defrag %d", SDFAT_SB(sb)->options.defrag); + __unlock_super(sb); + return -EPERM; + } + __unlock_super(sb); + + return sdfat_ioctl_defrag_trav(inode, uarg); + } + case SDFAT_IOCTL_DFR_REQ: { + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &SDFAT_SB(sb)->fsi; + unsigned int __user *uarg = (unsigned int __user *) arg; + + __lock_super(sb); + + /* Check if FS_ERROR occurred */ + if (sb->s_flags & MS_RDONLY) { + dfr_err("RDONLY partition (err %d)", -EPERM); + __unlock_super(sb); + return -EPERM; + } + + /* Check FS type (FAT32 only) */ + if (fsi->vol_type != FAT32) { + dfr_err("Defrag not supported, vol_type %d", fsi->vol_type); + __unlock_super(sb); + return -EINVAL; + + } + + /* Check if SB's defrag option enabled */ + if (!(SDFAT_SB(sb)->options.defrag)) { + dfr_err("Defrag not supported, sbi->options.defrag %d", SDFAT_SB(sb)->options.defrag); + __unlock_super(sb); + return -EPERM; + } + + /* Only IOCTL on mount-point allowed */ + if (filp->f_path.mnt->mnt_root != filp->f_path.dentry) { + dfr_err("IOC_DFR_INFO only allowed on ROOT, root %p, dentry %p", + filp->f_path.mnt->mnt_root, filp->f_path.dentry); + __unlock_super(sb); + return -EINVAL; + } + __unlock_super(sb); + + return sdfat_ioctl_defrag_req(inode, uarg); + } +#ifdef CONFIG_SDFAT_DFR_DEBUG + case SDFAT_IOCTL_DFR_SPO_FLAG: { + struct sdfat_sb_info *sbi = SDFAT_SB(inode->i_sb); + int ret = 0; + + ret = get_user(sbi->dfr_spo_flag, (int __user *)arg); + dfr_debug("dfr_spo_flag %d", sbi->dfr_spo_flag); + + return ret; + } +#endif /* CONFIG_SDFAT_DFR_DEBUG */ + } +#endif /* CONFIG_SDFAT_DFR */ + + /* Inappropriate ioctl for device */ + return -ENOTTY; +} + +static int sdfat_dbg_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ +#ifdef CONFIG_SDFAT_DBG_IOCTL + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + unsigned int flags; + + switch (cmd) { + case SDFAT_IOC_GET_DEBUGFLAGS: + flags = sbi->debug_flags; + return put_user(flags, (int __user *)arg); + case SDFAT_IOC_SET_DEBUGFLAGS: + flags = 0; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (get_user(flags, (int __user *) arg)) + return -EFAULT; + + __lock_super(sb); + sbi->debug_flags = flags; + __unlock_super(sb); + return 0; + case SDFAT_IOCTL_PANIC: + panic("ioctl panic for test"); + + /* COULD NOT REACH HEAR */ + return 0; + } +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + return -ENOTTY; +} + +static long sdfat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + int err; + + if (cmd == SDFAT_IOCTL_GET_VOLUME_ID) + return sdfat_ioctl_volume_id(inode); + + err = sdfat_dfr_ioctl(inode, filp, cmd, arg); + if (err != -ENOTTY) + return err; + + /* -ENOTTY if inappropriate ioctl for device */ + return sdfat_dbg_ioctl(inode, filp, cmd, arg); +} + + +static void __sdfat_writepage_end_io(struct bio *bio, int err) +{ + struct page *page = bio->bi_io_vec->bv_page; + struct super_block *sb = page->mapping->host->i_sb; + + ASSERT(bio->bi_vcnt == 1); /* Single page endio */ + ASSERT(bio_data_dir(bio)); /* Write */ + + if (err) { + SetPageError(page); + mapping_set_error(page->mapping, err); + } + + __dfr_writepage_end_io(page); + +#ifdef CONFIG_SDFAT_TRACE_IO + { + //struct sdfat_sb_info *sbi = SDFAT_SB(bio->bi_bdev->bd_super); + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + sbi->stat_n_pages_written++; + if (page->mapping->host == sb->s_bdev->bd_inode) + sbi->stat_n_bdev_pages_written++; + + /* 4 MB = 1024 pages => 0.4 sec (approx.) + * 32 KB = 64 pages => 0.025 sec + * Min. average latency b/w msgs. ~= 0.025 sec + */ + if ((sbi->stat_n_pages_written & 63) == 0) { + DMSG("STAT:%u, %u, %u, %u (Sector #: %u)\n", + sbi->stat_n_pages_added, sbi->stat_n_pages_written, + sbi->stat_n_bdev_pages_witten, + sbi->stat_n_pages_confused, + (unsigned int)__sdfat_bio_sector(bio)); + } + } +#endif + end_page_writeback(page); + bio_put(bio); + + // Update trace info. + atomic_dec(&SDFAT_SB(sb)->stat_n_pages_queued); +} + + +static int __support_write_inode_sync(struct super_block *sb) +{ +#ifdef CONFIG_SDFAT_SUPPORT_DIR_SYNC +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + if (sbi->fsi.vol_type != EXFAT) + return 0; +#endif + return 1; +#endif + return 0; +} + + +static int __sdfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) +{ + struct inode *inode = filp->f_mapping->host; + struct super_block *sb = inode->i_sb; + int res, err = 0; + + res = __sdfat_generic_file_fsync(filp, start, end, datasync); + + if (!__support_write_inode_sync(sb)) + err = fsapi_sync_fs(sb, 1); + + return res ? res : err; +} + + +static const struct file_operations sdfat_dir_operations = { + .llseek = generic_file_llseek, + .read = generic_read_dir, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + .iterate = sdfat_iterate, +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) */ + .readdir = sdfat_readdir, +#endif + .fsync = sdfat_file_fsync, + .unlocked_ioctl = sdfat_generic_ioctl, +}; + +static int __sdfat_create(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + FILE_ID_T fid; + loff_t i_pos; + int err; + + __lock_super(sb); + + TMSG("%s entered\n", __func__); + + err = fsapi_create(dir, (u8 *) dentry->d_name.name, FM_REGULAR, &fid); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + dir->i_version++; + dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) sdfat_sync_inode(dir); + else + mark_inode_dirty(dir); + + i_pos = sdfat_make_i_pos(&fid); + inode = sdfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + inode->i_version++; + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + d_instantiate(dentry, inode); +out: + __unlock_d_revalidate(dentry); + __unlock_super(sb); + TMSG("%s exited with err(%d)\n", __func__, err); + if (!err) + sdfat_statistics_set_create(fid.flags); + return err; +} + + +static int sdfat_find(struct inode *dir, struct qstr *qname, FILE_ID_T *fid) +{ + int err; + + if (qname->len == 0) + return -ENOENT; + + err = fsapi_lookup(dir, (u8 *) qname->name, fid); + if (err) + return -ENOENT; + + return 0; +} + +static int sdfat_d_anon_disconn(struct dentry *dentry) +{ + return IS_ROOT(dentry) && (dentry->d_flags & DCACHE_DISCONNECTED); +} + +static struct dentry *__sdfat_lookup(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + struct dentry *alias; + int err; + FILE_ID_T fid; + loff_t i_pos; + u64 ret; + mode_t i_mode; + + __lock_super(sb); + TMSG("%s entered\n", __func__); + err = sdfat_find(dir, &dentry->d_name, &fid); + if (err) { + if (err == -ENOENT) { + inode = NULL; + goto out; + } + goto error; + } + + i_pos = sdfat_make_i_pos(&fid); + inode = sdfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto error; + } + + i_mode = inode->i_mode; + if (S_ISLNK(i_mode) && !SDFAT_I(inode)->target) { + SDFAT_I(inode)->target = kmalloc((i_size_read(inode)+1), GFP_KERNEL); + if (!SDFAT_I(inode)->target) { + err = -ENOMEM; + goto error; + } + fsapi_read_link(dir, &fid, SDFAT_I(inode)->target, i_size_read(inode), &ret); + *(SDFAT_I(inode)->target + i_size_read(inode)) = '\0'; + } + + alias = d_find_alias(inode); + + /* + * Checking "alias->d_parent == dentry->d_parent" to make sure + * FS is not corrupted (especially double linked dir). + */ + if (alias && alias->d_parent == dentry->d_parent && + !sdfat_d_anon_disconn(alias)) { + + /* + * Unhashed alias is able to exist because of revalidate() + * called by lookup_fast. You can easily make this status + * by calling create and lookup concurrently + * In such case, we reuse an alias instead of new dentry + */ + if (d_unhashed(alias)) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) + BUG_ON(alias->d_name.hash != dentry->d_name.hash && alias->d_name.len != dentry->d_name.len); +#else + BUG_ON(alias->d_name.hash_len != dentry->d_name.hash_len); +#endif + sdfat_msg(sb, KERN_INFO, "rehashed a dentry(%p) " + "in read lookup", alias); + d_drop(dentry); + d_rehash(alias); + } else if (!S_ISDIR(i_mode)) { + /* + * This inode has non anonymous-DCACHE_DISCONNECTED + * dentry. This means, the user did ->lookup() by an + * another name (longname vs 8.3 alias of it) in past. + * + * Switch to new one for reason of locality if possible. + */ + d_move(alias, dentry); + } + iput(inode); + __unlock_super(sb); + TMSG("%s exited\n", __func__); + return alias; + } + dput(alias); +out: + /* initialize d_time even though it is positive dentry */ + dentry->d_time = dir->i_version; + __unlock_super(sb); + + dentry = d_splice_alias(inode, dentry); + + TMSG("%s exited\n", __func__); + return dentry; +error: + __unlock_super(sb); + TMSG("%s exited with err(%d)\n", __func__, err); + return ERR_PTR(err); +} + + +static int sdfat_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + struct super_block *sb = dir->i_sb; + int err; + + __lock_super(sb); + + TMSG("%s entered\n", __func__); + + SDFAT_I(inode)->fid.size = i_size_read(inode); + + __cancel_dfr_work(inode, 0, SDFAT_I(inode)->fid.size, __func__); + + err = fsapi_unlink(dir, &(SDFAT_I(inode)->fid)); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + dir->i_version++; + dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) sdfat_sync_inode(dir); + else + mark_inode_dirty(dir); + + clear_nlink(inode); + inode->i_mtime = inode->i_atime = current_time(inode); + sdfat_detach(inode); + dentry->d_time = dir->i_version; +out: + __unlock_d_revalidate(dentry); + __unlock_super(sb); + TMSG("%s exited with err(%d)\n", __func__, err); + return err; +} + +static int sdfat_symlink(struct inode *dir, struct dentry *dentry, const char *target) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + FILE_ID_T fid; + loff_t i_pos; + int err; + u64 len = (u64) strlen(target); + u64 ret; + + /* symlink option check */ + if (!SDFAT_SB(sb)->options.symlink) + return -ENOTSUPP; + + __lock_super(sb); + + TMSG("%s entered\n", __func__); + + err = fsapi_create(dir, (u8 *) dentry->d_name.name, FM_SYMLINK, &fid); + if (err) + goto out; + + err = fsapi_write_link(dir, &fid, (char *) target, len, &ret); + + if (err) { + fsapi_remove(dir, &fid); + goto out; + } + + __lock_d_revalidate(dentry); + + dir->i_version++; + dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) sdfat_sync_inode(dir); + else + mark_inode_dirty(dir); + + i_pos = sdfat_make_i_pos(&fid); + inode = sdfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + inode->i_version++; + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + SDFAT_I(inode)->target = kmalloc((len+1), GFP_KERNEL); + if (!SDFAT_I(inode)->target) { + err = -ENOMEM; + goto out; + } + memcpy(SDFAT_I(inode)->target, target, len+1); + + d_instantiate(dentry, inode); +out: + __unlock_d_revalidate(dentry); + __unlock_super(sb); + TMSG("%s exited with err(%d)\n", __func__, err); + return err; +} + + +static int __sdfat_mkdir(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + FILE_ID_T fid; + loff_t i_pos; + int err; + + __lock_super(sb); + + TMSG("%s entered\n", __func__); + + err = fsapi_mkdir(dir, (u8 *) dentry->d_name.name, &fid); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + dir->i_version++; + dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) sdfat_sync_inode(dir); + else + mark_inode_dirty(dir); + inc_nlink(dir); + + i_pos = sdfat_make_i_pos(&fid); + inode = sdfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + inode->i_version++; + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + d_instantiate(dentry, inode); + +out: + __unlock_d_revalidate(dentry); + __unlock_super(sb); + TMSG("%s exited with err(%d)\n", __func__, err); + if (!err) + sdfat_statistics_set_mkdir(fid.flags); + return err; +} + + +static int sdfat_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + struct super_block *sb = dir->i_sb; + int err; + + __lock_super(sb); + + TMSG("%s entered\n", __func__); + + SDFAT_I(inode)->fid.size = i_size_read(inode); + + err = fsapi_rmdir(dir, &(SDFAT_I(inode)->fid)); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + dir->i_version++; + dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) sdfat_sync_inode(dir); + else + mark_inode_dirty(dir); + drop_nlink(dir); + + clear_nlink(inode); + inode->i_mtime = inode->i_atime = current_time(inode); + sdfat_detach(inode); + dentry->d_time = dir->i_version; +out: + __unlock_d_revalidate(dentry); + __unlock_super(sb); + TMSG("%s exited with err(%d)\n", __func__, err); + return err; +} + +static int __sdfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + struct inode *old_inode, *new_inode; + struct super_block *sb = old_dir->i_sb; + loff_t i_pos; + int err; + + __lock_super(sb); + + TMSG("%s entered\n", __func__); + + old_inode = old_dentry->d_inode; + new_inode = new_dentry->d_inode; + + SDFAT_I(old_inode)->fid.size = i_size_read(old_inode); + + __cancel_dfr_work(old_inode, 0, 1, __func__); + + err = fsapi_rename(old_dir, &(SDFAT_I(old_inode)->fid), new_dir, new_dentry); + if (err) + goto out; + + __lock_d_revalidate(old_dentry); + __lock_d_revalidate(new_dentry); + + new_dir->i_version++; + new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = current_time(new_dir); + if (IS_DIRSYNC(new_dir)) + (void) sdfat_sync_inode(new_dir); + else + mark_inode_dirty(new_dir); + + i_pos = sdfat_make_i_pos(&(SDFAT_I(old_inode)->fid)); + sdfat_detach(old_inode); + sdfat_attach(old_inode, i_pos); + if (IS_DIRSYNC(new_dir)) + (void) sdfat_sync_inode(old_inode); + else + mark_inode_dirty(old_inode); + + if ((S_ISDIR(old_inode->i_mode)) && (old_dir != new_dir)) { + drop_nlink(old_dir); + if (!new_inode) + inc_nlink(new_dir); + } + + old_dir->i_version++; + old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); + if (IS_DIRSYNC(old_dir)) + (void) sdfat_sync_inode(old_dir); + else + mark_inode_dirty(old_dir); + + if (new_inode) { + sdfat_detach(new_inode); + + /* skip drop_nlink if new_inode already has been dropped */ + if (new_inode->i_nlink) { + drop_nlink(new_inode); + if (S_ISDIR(new_inode->i_mode)) + drop_nlink(new_inode); + } else { + EMSG("%s : abnormal access to an inode dropped\n", + __func__); + WARN_ON(new_inode->i_nlink == 0); + } + new_inode->i_ctime = current_time(new_inode); +#if 0 + (void) sdfat_sync_inode(new_inode); +#endif + } + +out: + __unlock_d_revalidate(old_dentry); + __unlock_d_revalidate(new_dentry); + __unlock_super(sb); + TMSG("%s exited with err(%d)\n", __func__, err); + return err; +} + +static int sdfat_cont_expand(struct inode *inode, loff_t size) +{ + struct address_space *mapping = inode->i_mapping; + loff_t start = i_size_read(inode), count = size - i_size_read(inode); + int err, err2; + + err = generic_cont_expand_simple(inode, size); + if (err) + return err; + + inode->i_ctime = inode->i_mtime = current_time(inode); + mark_inode_dirty(inode); + + if (!IS_SYNC(inode)) + return 0; + + err = filemap_fdatawrite_range(mapping, start, start + count - 1); + err2 = sync_mapping_buffers(mapping); + err = (err)?(err):(err2); + err2 = write_inode_now(inode, 1); + err = (err)?(err):(err2); + if (err) + return err; + + return filemap_fdatawait_range(mapping, start, start + count - 1); +} + +static int sdfat_allow_set_time(struct sdfat_sb_info *sbi, struct inode *inode) +{ + mode_t allow_utime = sbi->options.allow_utime; + + if (!uid_eq(current_fsuid(), inode->i_uid)) { + if (in_group_p(inode->i_gid)) + allow_utime >>= 3; + if (allow_utime & MAY_WRITE) + return 1; + } + + /* use a default check */ + return 0; +} + +static int sdfat_sanitize_mode(const struct sdfat_sb_info *sbi, + struct inode *inode, umode_t *mode_ptr) +{ + mode_t i_mode, mask, perm; + + i_mode = inode->i_mode; + + if (S_ISREG(i_mode) || S_ISLNK(i_mode)) + mask = sbi->options.fs_fmask; + else + mask = sbi->options.fs_dmask; + + perm = *mode_ptr & ~(S_IFMT | mask); + + /* Of the r and x bits, all (subject to umask) must be present.*/ + if ((perm & (S_IRUGO | S_IXUGO)) != (i_mode & (S_IRUGO | S_IXUGO))) + return -EPERM; + + if (sdfat_mode_can_hold_ro(inode)) { + /* Of the w bits, either all (subject to umask) or none must be present. */ + if ((perm & S_IWUGO) && ((perm & S_IWUGO) != (S_IWUGO & ~mask))) + return -EPERM; + } else { + /* If sdfat_mode_can_hold_ro(inode) is false, can't change w bits. */ + if ((perm & S_IWUGO) != (S_IWUGO & ~mask)) + return -EPERM; + } + + *mode_ptr &= S_IFMT | perm; + + return 0; +} + +static int sdfat_setattr(struct dentry *dentry, struct iattr *attr) +{ + + struct sdfat_sb_info *sbi = SDFAT_SB(dentry->d_sb); + struct inode *inode = dentry->d_inode; + unsigned int ia_valid; + int error; + loff_t old_size; + + TMSG("%s entered\n", __func__); + + if ((attr->ia_valid & ATTR_SIZE) + && (attr->ia_size > i_size_read(inode))) { + error = sdfat_cont_expand(inode, attr->ia_size); + if (error || attr->ia_valid == ATTR_SIZE) + return error; + attr->ia_valid &= ~ATTR_SIZE; + } + + /* Check for setting the inode time. */ + ia_valid = attr->ia_valid; + if ((ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) + && sdfat_allow_set_time(sbi, inode)) { + attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET); + } + + error = setattr_prepare(dentry, attr); + attr->ia_valid = ia_valid; + if (error) + return error; + + if (((attr->ia_valid & ATTR_UID) && + (!uid_eq(attr->ia_uid, sbi->options.fs_uid))) || + ((attr->ia_valid & ATTR_GID) && + (!gid_eq(attr->ia_gid, sbi->options.fs_gid))) || + ((attr->ia_valid & ATTR_MODE) && + (attr->ia_mode & ~(S_IFREG | S_IFLNK | S_IFDIR | S_IRWXUGO)))) { + return -EPERM; + } + + /* + * We don't return -EPERM here. Yes, strange, but this is too + * old behavior. + */ + if (attr->ia_valid & ATTR_MODE) { + if (sdfat_sanitize_mode(sbi, inode, &attr->ia_mode) < 0) + attr->ia_valid &= ~ATTR_MODE; + } + + SDFAT_I(inode)->fid.size = i_size_read(inode); + + /* patch 1.2.0 : fixed the problem of size mismatch. */ + if (attr->ia_valid & ATTR_SIZE) { + old_size = i_size_read(inode); + + /* TO CHECK evicting directory works correctly */ + MMSG("%s: inode(%p) truncate size (%llu->%llu)\n", __func__, + inode, (u64)old_size, (u64)attr->ia_size); + __sdfat_do_truncate(inode, old_size, attr->ia_size); + } + setattr_copy(inode, attr); + mark_inode_dirty(inode); + + + TMSG("%s exited with err(%d)\n", __func__, error); + return error; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +static int sdfat_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) +{ + struct inode *inode = path->dentry->d_inode; +#else +static int sdfat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) +{ + struct inode *inode = dentry->d_inode; +#endif + + TMSG("%s entered\n", __func__); + + generic_fillattr(inode, stat); + stat->blksize = SDFAT_SB(inode->i_sb)->fsi.cluster_size; + + TMSG("%s exited\n", __func__); + return 0; +} + +static const struct inode_operations sdfat_dir_inode_operations = { + .create = sdfat_create, + .lookup = sdfat_lookup, + .unlink = sdfat_unlink, + .symlink = sdfat_symlink, + .mkdir = sdfat_mkdir, + .rmdir = sdfat_rmdir, + .rename = sdfat_rename, + .setattr = sdfat_setattr, + .getattr = sdfat_getattr, +#ifdef CONFIG_SDFAT_VIRTUAL_XATTR + .listxattr = sdfat_listxattr, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) + .setxattr = sdfat_setxattr, + .getxattr = sdfat_getxattr, + .removexattr = sdfat_removexattr, +#endif +#endif +}; + +/*======================================================================*/ +/* File Operations */ +/*======================================================================*/ +static const struct inode_operations sdfat_symlink_inode_operations = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) + .readlink = generic_readlink, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) + .get_link = sdfat_follow_link, +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) */ + .follow_link = sdfat_follow_link, +#endif +#ifdef CONFIG_SDFAT_VIRTUAL_XATTR + .listxattr = sdfat_listxattr, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) + .setxattr = sdfat_setxattr, + .getxattr = sdfat_getxattr, + .removexattr = sdfat_removexattr, +#endif +#endif +}; + +static int sdfat_file_release(struct inode *inode, struct file *filp) +{ + struct super_block *sb = inode->i_sb; + + /* Moved below code from sdfat_write_inode + * TO FIX size-mismatch problem. + */ + /* FIXME : Added bug_on to confirm that there is no size mismatch */ + sdfat_debug_bug_on(SDFAT_I(inode)->fid.size != i_size_read(inode)); + SDFAT_I(inode)->fid.size = i_size_read(inode); + fsapi_sync_fs(sb, 0); + return 0; +} + +static const struct file_operations sdfat_file_operations = { + .llseek = generic_file_llseek, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) + .read = new_sync_read, + .write = new_sync_write, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) */ + .read = do_sync_read, + .write = do_sync_write, + .aio_read = generic_file_aio_read, + .aio_write = generic_file_aio_write, +#endif + .mmap = sdfat_file_mmap, + .release = sdfat_file_release, + .unlocked_ioctl = sdfat_generic_ioctl, + .fsync = sdfat_file_fsync, + .splice_read = generic_file_splice_read, +}; + +static const struct address_space_operations sdfat_da_aops; +static const struct address_space_operations sdfat_aops; + +static void sdfat_truncate(struct inode *inode, loff_t old_size) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + unsigned int blocksize = 1 << inode->i_blkbits; + loff_t aligned_size; + int err; + + __lock_super(sb); + + if (SDFAT_I(inode)->fid.start_clu == 0) { + /* Stange statement: + * Empty start_clu != ~0 (not allocated) + */ + sdfat_fs_error(sb, "tried to truncate zeroed cluster."); + goto out; + } + + sdfat_debug_check_clusters(inode); + + __cancel_dfr_work(inode, (loff_t)i_size_read(inode), (loff_t)old_size, __func__); + + err = fsapi_truncate(inode, old_size, i_size_read(inode)); + if (err) + goto out; + + inode->i_ctime = inode->i_mtime = current_time(inode); + if (IS_DIRSYNC(inode)) + (void) sdfat_sync_inode(inode); + else + mark_inode_dirty(inode); + + // FIXME: 확인 요망 + // inode->i_blocks = ((SDFAT_I(inode)->i_size_ondisk + (fsi->cluster_size - 1)) + inode->i_blocks = ((i_size_read(inode) + (fsi->cluster_size - 1)) & + ~((loff_t)fsi->cluster_size - 1)) >> inode->i_blkbits; +out: + /* + * This protects against truncating a file bigger than it was then + * trying to write into the hole. + * + * comment by sh.hong: + * This seems to mean 'intra page/block' truncate and writing. + * I couldn't find a reason to change the values prior to fsapi_truncate + * Therefore, I switched the order of operations + * so that it's possible to utilize i_size_ondisk in fsapi_truncate + */ + + aligned_size = i_size_read(inode); + if (aligned_size & (blocksize - 1)) { + aligned_size |= (blocksize - 1); + aligned_size++; + } + + if (SDFAT_I(inode)->i_size_ondisk > i_size_read(inode)) + SDFAT_I(inode)->i_size_ondisk = aligned_size; + + sdfat_debug_check_clusters(inode); + + if (SDFAT_I(inode)->i_size_aligned > i_size_read(inode)) + SDFAT_I(inode)->i_size_aligned = aligned_size; + + /* After truncation : + * 1) Delayed allocation is OFF + * i_size = i_size_ondisk <= i_size_aligned + * (useless size var.) + * (block-aligned) + * 2) Delayed allocation is ON + * i_size = i_size_ondisk = i_size_aligned + * (will be block-aligned after write) + * or + * i_size_ondisk < i_size <= i_size_aligned (block_aligned) + * (will be block-aligned after write) + */ + + __unlock_super(sb); +} + +static const struct inode_operations sdfat_file_inode_operations = { + .setattr = sdfat_setattr, + .getattr = sdfat_getattr, +#ifdef CONFIG_SDFAT_VIRTUAL_XATTR + .listxattr = sdfat_listxattr, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) + .setxattr = sdfat_setxattr, + .getxattr = sdfat_getxattr, + .removexattr = sdfat_removexattr, +#endif +#endif +}; + +/*======================================================================*/ +/* Address Space Operations */ +/*======================================================================*/ +/* 2-level option flag */ +#define BMAP_NOT_CREATE 0 +#define BMAP_ADD_BLOCK 1 +#define BMAP_ADD_CLUSTER 2 +#define BLOCK_ADDED(bmap_ops) (bmap_ops) +static int sdfat_bmap(struct inode *inode, sector_t sector, sector_t *phys, + unsigned long *mapped_blocks, int *create) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + const unsigned long blocksize = sb->s_blocksize; + const unsigned char blocksize_bits = sb->s_blocksize_bits; + sector_t last_block; + unsigned int cluster, clu_offset, sec_offset; + int err = 0; + + *phys = 0; + *mapped_blocks = 0; + + /* core code should handle EIO */ +#if 0 + if (fsi->prev_eio && BLOCK_ADDED(*create)) + return -EIO; +#endif + + if (((fsi->vol_type == FAT12) || (fsi->vol_type == FAT16)) && + (inode->i_ino == SDFAT_ROOT_INO)) { + if (sector < (fsi->dentries_in_root >> + (sb->s_blocksize_bits-DENTRY_SIZE_BITS))) { + *phys = sector + fsi->root_start_sector; + *mapped_blocks = 1; + } + return 0; + } + + last_block = (i_size_read(inode) + (blocksize - 1)) >> blocksize_bits; + if ((sector >= last_block) && (*create == BMAP_NOT_CREATE)) + return 0; + + /* Is this block already allocated? */ + clu_offset = sector >> fsi->sect_per_clus_bits; /* cluster offset */ + + SDFAT_I(inode)->fid.size = i_size_read(inode); + + + if (unlikely(__check_dfr_on(inode, + (loff_t)((loff_t)clu_offset << fsi->cluster_size_bits), + (loff_t)((loff_t)(clu_offset + 1) << fsi->cluster_size_bits), + __func__))) { + err = __do_dfr_map_cluster(inode, clu_offset, &cluster); + } else { + if (*create & BMAP_ADD_CLUSTER) + err = fsapi_map_clus(inode, clu_offset, &cluster, 1); + else + err = fsapi_map_clus(inode, clu_offset, &cluster, ALLOC_NOWHERE); + } + + if (err) { + if (err != -ENOSPC) + return -EIO; + return err; + } + + /* FOR BIGDATA */ + sdfat_statistics_set_rw(SDFAT_I(inode)->fid.flags, + clu_offset, *create & BMAP_ADD_CLUSTER); + + if (!IS_CLUS_EOF(cluster)) { + /* sector offset in cluster */ + sec_offset = sector & (fsi->sect_per_clus - 1); + + *phys = CLUS_TO_SECT(fsi, cluster) + sec_offset; + *mapped_blocks = fsi->sect_per_clus - sec_offset; + } +#if 0 + else { + /* Debug purpose (new clu needed) */ + ASSERT((*create & BMAP_ADD_CLUSTER) == 0); + ASSERT(sector >= last_block); + } +#endif + + if (sector < last_block) + *create = BMAP_NOT_CREATE; +#if 0 + else if (sector >= last_block) + *create = non-zero; + + if (iblock <= last mapped-block) + *phys != 0 + *create = BMAP_NOT_CREATE + else if (iblock <= last cluster) + *phys != 0 + *create = non-zero +#endif + return 0; +} + +static int sdfat_da_prep_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits; + unsigned long mapped_blocks; + sector_t phys; + loff_t pos; + int sec_offset; + int bmap_create = create ? BMAP_ADD_BLOCK : BMAP_NOT_CREATE; + int err = 0; + + __lock_super(sb); + + /* FAT32 only */ + ASSERT(fsi->vol_type == FAT32); + + err = sdfat_bmap(inode, iblock, &phys, &mapped_blocks, &bmap_create); + if (err) { + if (err != -ENOSPC) + sdfat_fs_error_ratelimit(sb, "%s: failed to bmap " + "(iblock:%u, err:%d)", __func__, + (u32)iblock, err); + goto unlock_ret; + } + + sec_offset = iblock & (fsi->sect_per_clus - 1); + + if (phys) { + /* the block in in the mapped cluster boundary */ + max_blocks = min(mapped_blocks, max_blocks); + map_bh(bh_result, sb, phys); + + BUG_ON(BLOCK_ADDED(bmap_create) && (sec_offset == 0)); + + } else if (create == 1) { + /* Not exist: new cluster needed */ + BUG_ON(!BLOCK_ADDED(bmap_create)); + + // Reserved Cluster (only if iblock is the first sector in a clu) + if (sec_offset == 0) { + err = fsapi_reserve_clus(inode); + if (err) { + if (err != -ENOSPC) + sdfat_fs_error_ratelimit(sb, + "%s: failed to bmap " + "(iblock:%u, err:%d)", __func__, + (u32)iblock, err); + + goto unlock_ret; + } + } + + // Delayed mapping + map_bh(bh_result, sb, ~((sector_t) 0xffff)); + set_buffer_new(bh_result); + set_buffer_delay(bh_result); + + } else { + /* get_block on non-existing addr. with create==0 */ + /* + * CHECKME: + * i_size_aligned 보다 작으면 delay 매핑을 일단 + * 켜줘야되는 게 아닌가? + * - 0-fill 을 항상 하기에, FAT 에서는 문제 없음. + * 중간에 영역이 꽉 찼으면, 디스크에 내려가지 않고는 + * invalidate 될 일이 없음 + */ + goto unlock_ret; + } + + + /* Newly added blocks */ + if (BLOCK_ADDED(bmap_create)) { + set_buffer_new(bh_result); + + SDFAT_I(inode)->i_size_aligned += max_blocks << sb->s_blocksize_bits; + if (phys) { + /* i_size_ondisk changes if a block added in the existing cluster */ + #define num_clusters(value) ((value) ? (s32)((value - 1) >> fsi->cluster_size_bits) + 1 : 0) + + /* FOR GRACEFUL ERROR HANDLING */ + if (num_clusters(SDFAT_I(inode)->i_size_aligned) != + num_clusters(SDFAT_I(inode)->i_size_ondisk)) { + EMSG("%s: inode(%p) invalid size (create(%d) " + "bmap_create(%d) phys(%lld) aligned(%lld) " + "on_disk(%lld) iblock(%u) sec_off(%d))\n", + __func__, inode, create, bmap_create, (s64)phys, + (s64)SDFAT_I(inode)->i_size_aligned, + (s64)SDFAT_I(inode)->i_size_ondisk, + (u32)iblock, + (s32)sec_offset); + sdfat_debug_bug_on(1); + } + SDFAT_I(inode)->i_size_ondisk = SDFAT_I(inode)->i_size_aligned; + } + + pos = (iblock + 1) << sb->s_blocksize_bits; + /* Debug purpose - defensive coding */ + ASSERT(SDFAT_I(inode)->i_size_aligned == pos); + if (SDFAT_I(inode)->i_size_aligned < pos) + SDFAT_I(inode)->i_size_aligned = pos; + /* Debug end */ + +#ifdef CONFIG_SDFAT_TRACE_IO + /* New page added (ASSERTION: 8 blocks per page) */ + if ((sec_offset & 7) == 0) + sbi->stat_n_pages_added++; +#endif + } + + /* FOR GRACEFUL ERROR HANDLING */ + if (i_size_read(inode) > SDFAT_I(inode)->i_size_aligned) { + sdfat_fs_error_ratelimit(sb, "%s: invalid size (inode(%p), " + "size(%llu) > aligned(%llu)\n", __func__, inode, + i_size_read(inode), SDFAT_I(inode)->i_size_aligned); + sdfat_debug_bug_on(1); + } + + bh_result->b_size = max_blocks << sb->s_blocksize_bits; + +unlock_ret: + __unlock_super(sb); + return err; +} + +static int sdfat_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + struct super_block *sb = inode->i_sb; + unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits; + int err = 0; + unsigned long mapped_blocks; + sector_t phys; + loff_t pos; + int bmap_create = create ? BMAP_ADD_CLUSTER : BMAP_NOT_CREATE; + + __lock_super(sb); + err = sdfat_bmap(inode, iblock, &phys, &mapped_blocks, &bmap_create); + if (err) { + if (err != -ENOSPC) + sdfat_fs_error_ratelimit(sb, "%s: failed to bmap " + "(inode:%p iblock:%u, err:%d)", + __func__, inode, (u32)iblock, err); + goto unlock_ret; + } + + if (phys) { + max_blocks = min(mapped_blocks, max_blocks); + + /* Treat newly added block / cluster */ + if (BLOCK_ADDED(bmap_create) || buffer_delay(bh_result)) { + + /* Update i_size_ondisk */ + pos = (iblock + 1) << sb->s_blocksize_bits; + if (SDFAT_I(inode)->i_size_ondisk < pos) { + /* Debug purpose */ + if ((pos - SDFAT_I(inode)->i_size_ondisk) > bh_result->b_size) { + /* This never happens without DA */ + MMSG("Jumping get_block\n"); + } + + SDFAT_I(inode)->i_size_ondisk = pos; + sdfat_debug_check_clusters(inode); + } + + if (BLOCK_ADDED(bmap_create)) { + /* Old way (w/o DA) + * create == 1 only if iblock > i_size + * (in block unit) + */ + + /* 20130723 CHECK + * Truncate와 동시에 발생할 경우, + * i_size < (i_block 위치) 면서 buffer_delay()가 + * 켜져있을 수 있다. + * + * 기존에 할당된 영역을 다시 쓸 뿐이므로 큰 문제 + * 없지만, 그 경우, 미리 i_size_aligned 가 확장된 + * 영역이어야 한다. + */ + + /* FOR GRACEFUL ERROR HANDLING */ + if (buffer_delay(bh_result) && + (pos > SDFAT_I(inode)->i_size_aligned)) { + sdfat_fs_error(sb, "requested for bmap " + "out of range(pos:(%llu)>i_size_aligned(%llu)\n", + pos, SDFAT_I(inode)->i_size_aligned); + sdfat_debug_bug_on(1); + err = -EIO; + goto unlock_ret; + } + set_buffer_new(bh_result); + + /* + * adjust i_size_aligned if i_size_ondisk is + * bigger than it. (i.e. non-DA) + */ + if (SDFAT_I(inode)->i_size_ondisk > + SDFAT_I(inode)->i_size_aligned) { + SDFAT_I(inode)->i_size_aligned = + SDFAT_I(inode)->i_size_ondisk; + } + } + + if (buffer_delay(bh_result)) + clear_buffer_delay(bh_result); + +#if 0 + /* Debug purpose */ + if (SDFAT_I(inode)->i_size_ondisk > + SDFAT_I(inode)->i_size_aligned) { + /* Only after truncate + * and the two size variables should indicate + * same i_block + */ + unsigned int blocksize = 1 << inode->i_blkbits; + BUG_ON(SDFAT_I(inode)->i_size_ondisk - + SDFAT_I(inode)->i_size_aligned >= blocksize); + } +#endif + } + map_bh(bh_result, sb, phys); + } + + bh_result->b_size = max_blocks << sb->s_blocksize_bits; +unlock_ret: + __unlock_super(sb); + return err; +} + +static int sdfat_readpage(struct file *file, struct page *page) +{ + int ret; + + ret = mpage_readpage(page, sdfat_get_block); + return ret; +} + +static int sdfat_readpages(struct file *file, struct address_space *mapping, + struct list_head *pages, unsigned int nr_pages) +{ + int ret; + + ret = mpage_readpages(mapping, pages, nr_pages, sdfat_get_block); + return ret; +} + +static inline void sdfat_submit_fullpage_bio(struct block_device *bdev, + sector_t sector, unsigned int length, struct page *page) +{ + /* Single page bio submit */ + struct bio *bio; + + BUG_ON((length > PAGE_SIZE) || (length == 0)); + + /* + * If __GFP_WAIT is set, then bio_alloc will always be able to allocate + * a bio. This is due to the mempool guarantees. To make this work, callers + * must never allocate more than 1 bio at a time from this pool. + * + * #define GFP_NOIO (__GFP_WAIT) + */ + bio = bio_alloc(GFP_NOIO, 1); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + bio_set_dev(bio, bdev); +#else + bio->bi_bdev = bdev; +#endif + bio->bi_vcnt = 1; + bio->bi_io_vec[0].bv_page = page; /* Inline vec */ + bio->bi_io_vec[0].bv_len = length; /* PAGE_SIZE */ + bio->bi_io_vec[0].bv_offset = 0; + __sdfat_set_bio_iterate(bio, sector, length, 0, 0); + + bio->bi_end_io = sdfat_writepage_end_io; + __sdfat_submit_bio_write(bio); +} + +static int sdfat_writepage(struct page *page, struct writeback_control *wbc) +{ + struct inode * const inode = page->mapping->host; + struct super_block *sb = inode->i_sb; + loff_t i_size = i_size_read(inode); + const pgoff_t end_index = i_size >> PAGE_SHIFT; + const unsigned int blocks_per_page = PAGE_SIZE >> inode->i_blkbits; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + struct buffer_head *bh, *head; + sector_t block, block_0, last_phys; + int ret; + unsigned int nr_blocks_towrite = blocks_per_page; + + /* Don't distinguish 0-filled/clean block. + * Just write back the whole page + */ + if (fsi->cluster_size < PAGE_SIZE) + goto confused; + + if (!PageUptodate(page)) { + MMSG("%s: Not up-to-date page -> block_write_full_page\n", + __func__); + goto confused; + } + + if (page->index >= end_index) { + /* last page or outside i_size */ + unsigned int offset = i_size & (PAGE_SIZE-1); + + /* If a truncation is in progress */ + if (page->index > end_index || !offset) + goto confused; + + /* 0-fill after i_size */ + zero_user_segment(page, offset, PAGE_SIZE); + } + + if (!page_has_buffers(page)) { + MMSG("WP: No buffers -> block_write_full_page\n"); + goto confused; + } + + block = (sector_t)page->index << (PAGE_SHIFT - inode->i_blkbits); + block_0 = block; /* first block */ + head = page_buffers(page); + bh = head; + + last_phys = 0; + do { + BUG_ON(buffer_locked(bh)); + + if (!buffer_dirty(bh) || !buffer_uptodate(bh)) { + if (nr_blocks_towrite == blocks_per_page) + nr_blocks_towrite = (unsigned int) (block - block_0); + + BUG_ON(nr_blocks_towrite >= blocks_per_page); + + // !uptodate but dirty?? + if (buffer_dirty(bh)) + goto confused; + + // Nothing to writeback in this block + bh = bh->b_this_page; + block++; + continue; + } + + if (nr_blocks_towrite != blocks_per_page) + // Dirty -> Non-dirty -> Dirty again case + goto confused; + + /* Map if needed */ + if (!buffer_mapped(bh) || buffer_delay(bh)) { + BUG_ON(bh->b_size != (1 << (inode->i_blkbits))); + ret = sdfat_get_block(inode, block, bh, 1); + if (ret) + goto confused; + + if (buffer_new(bh)) { + clear_buffer_new(bh); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + clean_bdev_bh_alias(bh); +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) */ + unmap_underlying_metadata(bh->b_bdev, bh->b_blocknr); +#endif + } + } + + /* continuity check */ + if (((last_phys + 1) != bh->b_blocknr) && (last_phys != 0)) { + DMSG("Non-contiguous block mapping in single page"); + goto confused; + } + + last_phys = bh->b_blocknr; + bh = bh->b_this_page; + block++; + } while (bh != head); + + if (nr_blocks_towrite == 0) { + DMSG("Page dirty but no dirty bh? alloc_208\n"); + goto confused; + } + + + /* Write-back */ + do { + clear_buffer_dirty(bh); + bh = bh->b_this_page; + } while (bh != head); + + BUG_ON(PageWriteback(page)); + set_page_writeback(page); + + /** + * Turn off MAPPED flag in victim's bh if defrag on. + * Another write_begin can starts after get_block for defrag victims called. + * In this case, write_begin calls get_block and get original block number + * and previous defrag will be canceled. + */ + if (unlikely(__check_dfr_on(inode, + (loff_t)(page->index << PAGE_SHIFT), + (loff_t)((page->index + 1) << PAGE_SHIFT), + __func__))) { + do { + clear_buffer_mapped(bh); + bh = bh->b_this_page; + } while (bh != head); + } + + // Trace # of pages queued (Approx.) + atomic_inc(&SDFAT_SB(sb)->stat_n_pages_queued); + + sdfat_submit_fullpage_bio(head->b_bdev, + head->b_blocknr << (inode->i_blkbits - sb->s_blocksize_bits), + nr_blocks_towrite << inode->i_blkbits, + page); + + unlock_page(page); + + return 0; + +confused: +#ifdef CONFIG_SDFAT_TRACE_IO + SDFAT_SB(sb)->stat_n_pages_confused++; +#endif + ret = block_write_full_page(page, sdfat_get_block, wbc); + return ret; +} + +static int sdfat_da_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + MMSG("%s(inode:%p) with nr_to_write = 0x%08lx " + "(ku %d, bg %d, tag %d, rc %d )\n", + __func__, mapping->host, wbc->nr_to_write, + wbc->for_kupdate, wbc->for_background, wbc->tagged_writepages, + wbc->for_reclaim); + + ASSERT(mapping->a_ops == &sdfat_da_aops); + +#ifdef CONFIG_SDFAT_ALIGNED_MPAGE_WRITE + if (SDFAT_SB(mapping->host->i_sb)->options.adj_req) + return sdfat_mpage_writepages(mapping, wbc, sdfat_get_block); +#endif + return generic_writepages(mapping, wbc); +} + +static int sdfat_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + MMSG("%s(inode:%p) with nr_to_write = 0x%08lx " + "(ku %d, bg %d, tag %d, rc %d )\n", + __func__, mapping->host, wbc->nr_to_write, + wbc->for_kupdate, wbc->for_background, wbc->tagged_writepages, + wbc->for_reclaim); + + ASSERT(mapping->a_ops == &sdfat_aops); + +#ifdef CONFIG_SDFAT_ALIGNED_MPAGE_WRITE + if (SDFAT_SB(mapping->host->i_sb)->options.adj_req) + return sdfat_mpage_writepages(mapping, wbc, sdfat_get_block); +#endif + return mpage_writepages(mapping, wbc, sdfat_get_block); +} + +static void sdfat_write_failed(struct address_space *mapping, loff_t to) +{ + struct inode *inode = mapping->host; + + if (to > i_size_read(inode)) { + __sdfat_truncate_pagecache(inode, to, i_size_read(inode)); + sdfat_truncate(inode, SDFAT_I(inode)->i_size_aligned); + } +} + +static int __sdfat_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, + unsigned int flags, struct page **pagep, + void **fsdata, get_block_t *get_block, + loff_t *bytes, const char *fname) +{ + int ret; + + __cancel_dfr_work(mapping->host, pos, (loff_t)(pos + len), fname); + + *pagep = NULL; + ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata, + get_block, bytes); + + if (ret < 0) + sdfat_write_failed(mapping, pos+len); + + return ret; +} + + +static int sdfat_da_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int flags, + struct page **pagep, void **fsdata) +{ + return __sdfat_write_begin(file, mapping, pos, len, flags, + pagep, fsdata, sdfat_da_prep_block, + &SDFAT_I(mapping->host)->i_size_aligned, + __func__); +} + + +static int sdfat_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int flags, + struct page **pagep, void **fsdata) +{ + return __sdfat_write_begin(file, mapping, pos, len, flags, + pagep, fsdata, sdfat_get_block, + &SDFAT_I(mapping->host)->i_size_ondisk, + __func__); +} + +static int sdfat_write_end(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int copied, + struct page *pagep, void *fsdata) +{ + struct inode *inode = mapping->host; + FILE_ID_T *fid = &(SDFAT_I(inode)->fid); + int err; + + err = generic_write_end(file, mapping, pos, len, copied, pagep, fsdata); + + /* FOR GRACEFUL ERROR HANDLING */ + if (SDFAT_I(inode)->i_size_aligned < i_size_read(inode)) { + sdfat_fs_error(inode->i_sb, "invalid size(size(%llu) " + "> aligned(%llu)\n", i_size_read(inode), + SDFAT_I(inode)->i_size_aligned); + sdfat_debug_bug_on(1); + } + + if (err < len) + sdfat_write_failed(mapping, pos+len); + + if (!(err < 0) && !(fid->attr & ATTR_ARCHIVE)) { + inode->i_mtime = inode->i_ctime = current_time(inode); + fid->attr |= ATTR_ARCHIVE; + mark_inode_dirty(inode); + } + + return err; +} + +static inline ssize_t __sdfat_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + loff_t count, unsigned long nr_segs) +{ + struct address_space *mapping = inode->i_mapping; + loff_t size = offset + count; + ssize_t ret; + + if (rw == WRITE) { + /* + * FIXME: blockdev_direct_IO() doesn't use ->write_begin(), + * so we need to update the ->i_size_aligned to block boundary. + * + * But we must fill the remaining area or hole by nul for + * updating ->i_size_aligned + * + * Return 0, and fallback to normal buffered write. + */ + if (SDFAT_I(inode)->i_size_aligned < size) + return 0; + } + + /* + * sdFAT need to use the DIO_LOCKING for avoiding the race + * condition of sdfat_get_block() and ->truncate(). + */ + ret = __sdfat_blkdev_direct_IO(rw, iocb, inode, iov_u, offset, nr_segs); + if (ret < 0 && (rw & WRITE)) + sdfat_write_failed(mapping, size); + + return ret; +} + +static const struct address_space_operations sdfat_aops = { + .readpage = sdfat_readpage, + .readpages = sdfat_readpages, + .writepage = sdfat_writepage, + .writepages = sdfat_writepages, + .write_begin = sdfat_write_begin, + .write_end = sdfat_write_end, + .direct_IO = sdfat_direct_IO, + .bmap = sdfat_aop_bmap +}; + +static const struct address_space_operations sdfat_da_aops = { + .readpage = sdfat_readpage, + .readpages = sdfat_readpages, + .writepage = sdfat_writepage, + .writepages = sdfat_da_writepages, + .write_begin = sdfat_da_write_begin, + .write_end = sdfat_write_end, + .direct_IO = sdfat_direct_IO, + .bmap = sdfat_aop_bmap +}; + +/*======================================================================*/ +/* Super Operations */ +/*======================================================================*/ + +static inline unsigned long sdfat_hash(loff_t i_pos) +{ + return hash_32(i_pos, SDFAT_HASH_BITS); +} + +static void sdfat_attach(struct inode *inode, loff_t i_pos) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(inode->i_sb); + struct hlist_head *head = sbi->inode_hashtable + sdfat_hash(i_pos); + + spin_lock(&sbi->inode_hash_lock); + SDFAT_I(inode)->i_pos = i_pos; + hlist_add_head(&SDFAT_I(inode)->i_hash_fat, head); + spin_unlock(&sbi->inode_hash_lock); +} + +static void sdfat_detach(struct inode *inode) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(inode->i_sb); + + spin_lock(&sbi->inode_hash_lock); + hlist_del_init(&SDFAT_I(inode)->i_hash_fat); + SDFAT_I(inode)->i_pos = 0; + spin_unlock(&sbi->inode_hash_lock); +} + + +/* doesn't deal with root inode */ +static int sdfat_fill_inode(struct inode *inode, const FILE_ID_T *fid) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(inode->i_sb); + FS_INFO_T *fsi = &(sbi->fsi); + DIR_ENTRY_T info; + u64 size = fid->size; + + memcpy(&(SDFAT_I(inode)->fid), fid, sizeof(FILE_ID_T)); + + SDFAT_I(inode)->i_pos = 0; + SDFAT_I(inode)->target = NULL; + inode->i_uid = sbi->options.fs_uid; + inode->i_gid = sbi->options.fs_gid; + inode->i_version++; + inode->i_generation = get_seconds(); + + if (fsapi_read_inode(inode, &info) < 0) { + MMSG("%s: failed to read stat!\n", __func__); + return -EIO; + } + + if (info.Attr & ATTR_SUBDIR) { /* directory */ + inode->i_generation &= ~1; + inode->i_mode = sdfat_make_mode(sbi, info.Attr, S_IRWXUGO); + inode->i_op = &sdfat_dir_inode_operations; + inode->i_fop = &sdfat_dir_operations; + + set_nlink(inode, info.NumSubdirs); + } else if (info.Attr & ATTR_SYMLINK) { /* symbolic link */ + inode->i_op = &sdfat_symlink_inode_operations; + inode->i_generation |= 1; + inode->i_mode = sdfat_make_mode(sbi, info.Attr, S_IRWXUGO); + } else { /* regular file */ + inode->i_generation |= 1; + inode->i_mode = sdfat_make_mode(sbi, info.Attr, S_IRWXUGO); + inode->i_op = &sdfat_file_inode_operations; + inode->i_fop = &sdfat_file_operations; + + if (sbi->options.improved_allocation & SDFAT_ALLOC_DELAY) + inode->i_mapping->a_ops = &sdfat_da_aops; + else + inode->i_mapping->a_ops = &sdfat_aops; + + inode->i_mapping->nrpages = 0; + + } + + /* + * Use fid->size instead of info.Size + * because info.Size means the value saved on disk + */ + i_size_write(inode, size); + + /* ondisk and aligned size should be aligned with block size */ + if (size & (inode->i_sb->s_blocksize - 1)) { + size |= (inode->i_sb->s_blocksize - 1); + size++; + } + + SDFAT_I(inode)->i_size_aligned = size; + SDFAT_I(inode)->i_size_ondisk = size; + sdfat_debug_check_clusters(inode); + + sdfat_save_attr(inode, info.Attr); + + inode->i_blocks = ((i_size_read(inode) + (fsi->cluster_size - 1)) + & ~((loff_t)fsi->cluster_size - 1)) >> inode->i_blkbits; + + sdfat_time_fat2unix(sbi, &inode->i_mtime, &info.ModifyTimestamp); + sdfat_time_fat2unix(sbi, &inode->i_ctime, &info.CreateTimestamp); + sdfat_time_fat2unix(sbi, &inode->i_atime, &info.AccessTimestamp); + + __init_dfr_info(inode); + + return 0; +} + +static struct inode *sdfat_build_inode(struct super_block *sb, + const FILE_ID_T *fid, loff_t i_pos) { + struct inode *inode; + int err; + + inode = sdfat_iget(sb, i_pos); + if (inode) + goto out; + inode = new_inode(sb); + if (!inode) { + inode = ERR_PTR(-ENOMEM); + goto out; + } + inode->i_ino = iunique(sb, SDFAT_ROOT_INO); + inode->i_version = 1; + err = sdfat_fill_inode(inode, fid); + if (err) { + iput(inode); + inode = ERR_PTR(err); + goto out; + } + sdfat_attach(inode, i_pos); + insert_inode_hash(inode); +out: + return inode; +} + +static struct inode *sdfat_alloc_inode(struct super_block *sb) +{ + struct sdfat_inode_info *ei; + + ei = kmem_cache_alloc(sdfat_inode_cachep, GFP_NOFS); + if (!ei) + return NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + init_rwsem(&ei->truncate_lock); +#endif + return &ei->vfs_inode; +} + +static void sdfat_destroy_inode(struct inode *inode) +{ + if (SDFAT_I(inode)->target) { + kfree(SDFAT_I(inode)->target); + SDFAT_I(inode)->target = NULL; + } + + kmem_cache_free(sdfat_inode_cachep, SDFAT_I(inode)); +} + +static int __sdfat_write_inode(struct inode *inode, int sync) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + DIR_ENTRY_T info; + + if (inode->i_ino == SDFAT_ROOT_INO) + return 0; + + info.Attr = sdfat_make_attr(inode); + info.Size = i_size_read(inode); + + sdfat_time_unix2fat(sbi, &inode->i_mtime, &info.ModifyTimestamp); + sdfat_time_unix2fat(sbi, &inode->i_ctime, &info.CreateTimestamp); + sdfat_time_unix2fat(sbi, &inode->i_atime, &info.AccessTimestamp); + + if (!__support_write_inode_sync(sb)) + sync = 0; + + /* FIXME : Do we need handling error? */ + return fsapi_write_inode(inode, &info, sync); +} + +static int sdfat_sync_inode(struct inode *inode) +{ + return __sdfat_write_inode(inode, 1); +} + +static int sdfat_write_inode(struct inode *inode, struct writeback_control *wbc) +{ + return __sdfat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL); +} + +static void sdfat_evict_inode(struct inode *inode) +{ + truncate_inode_pages(&inode->i_data, 0); + + if (!inode->i_nlink) { + loff_t old_size = i_size_read(inode); + + i_size_write(inode, 0); + + SDFAT_I(inode)->fid.size = old_size; + + __cancel_dfr_work(inode, 0, (loff_t)old_size, __func__); + + /* TO CHECK evicting directory works correctly */ + MMSG("%s: inode(%p) evict %s (size(%llu) to zero)\n", + __func__, inode, + S_ISDIR(inode->i_mode) ? "directory" : "file", + (u64)old_size); + fsapi_truncate(inode, old_size, 0); + } + + invalidate_inode_buffers(inode); + clear_inode(inode); + fsapi_invalidate_extent(inode); + sdfat_detach(inode); + + /* after end of this function, caller will remove inode hash */ + /* remove_inode_hash(inode); */ +} + + + +static void sdfat_put_super(struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + int err; + + sdfat_log_msg(sb, KERN_INFO, "trying to unmount..."); + + __cancel_delayed_work_sync(sbi); + + if (__is_sb_dirty(sb)) + sdfat_write_super(sb); + + __free_dfr_mem_if_required(sb); + err = fsapi_umount(sb); + + if (sbi->nls_disk) { + unload_nls(sbi->nls_disk); + sbi->nls_disk = NULL; + sbi->options.codepage = sdfat_default_codepage; + } + if (sbi->nls_io) { + unload_nls(sbi->nls_io); + sbi->nls_io = NULL; + } + if (sbi->options.iocharset != sdfat_default_iocharset) { + kfree(sbi->options.iocharset); + sbi->options.iocharset = sdfat_default_iocharset; + } + + sb->s_fs_info = NULL; + + kobject_del(&sbi->sb_kobj); + kobject_put(&sbi->sb_kobj); + if (!sbi->use_vmalloc) + kfree(sbi); + else + vfree(sbi); + + sdfat_log_msg(sb, KERN_INFO, "unmounted successfully! %s", + err ? "(with previous I/O errors)" : ""); +} + +static inline void __flush_delayed_meta(struct super_block *sb, s32 sync) +{ +#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY + fsapi_cache_flush(sb, sync); +#else + /* DO NOTHING */ +#endif +} + +static void sdfat_write_super(struct super_block *sb) +{ + int time = 0; + + __lock_super(sb); + + __set_sb_clean(sb); + +#ifdef CONFIG_SDFAT_DFR + if (atomic_read(&(SDFAT_SB(sb)->dfr_info.stat)) == DFR_SB_STAT_VALID) + fsapi_dfr_update_fat_next(sb); +#endif + + /* flush delayed FAT/DIR dirty */ + __flush_delayed_meta(sb, 0); + + if (!(sb->s_flags & MS_RDONLY)) + fsapi_sync_fs(sb, 0); + + __unlock_super(sb); + + time = jiffies; + + /* Issuing bdev requests is needed + * to guarantee DIR updates in time + * whether w/ or w/o delayed DIR dirty feature. + * (otherwise DIR updates could be delayed for 5 + 5 secs at max.) + */ + sync_blockdev(sb->s_bdev); + +#if (defined(CONFIG_SDFAT_DFR) && defined(CONFIG_SDFAT_DFR_DEBUG)) + /* SPO test */ + fsapi_dfr_spo_test(sb, DFR_SPO_FAT_NEXT, __func__); +#endif + MMSG("BD: sdfat_write_super (bdev_sync for %ld ms)\n", + (jiffies - time) * 1000 / HZ); +} + + +static void __dfr_update_fat_next(struct super_block *sb) +{ +#ifdef CONFIG_SDFAT_DFR + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + + if (sbi->options.defrag && + (atomic_read(&sbi->dfr_info.stat) == DFR_SB_STAT_VALID)) { + fsapi_dfr_update_fat_next(sb); + } +#endif +} + +static void __dfr_update_fat_prev(struct super_block *sb, int wait) +{ +#ifdef CONFIG_SDFAT_DFR + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct defrag_info *sb_dfr = &sbi->dfr_info; + /* static time available? */ + static int time; /* initialized by zero */ + int uevent = 0, total = 0, clean = 0, full = 0; + int spent = jiffies - time; + + if (!(sbi->options.defrag && wait)) + return; + + __lock_super(sb); + /* Update FAT for defrag */ + if (atomic_read(&(sbi->dfr_info.stat)) == DFR_SB_STAT_VALID) { + + fsapi_dfr_update_fat_prev(sb, 0); + + /* flush delayed FAT/DIR dirty */ + __flush_delayed_meta(sb, 0); + + /* Complete defrag req */ + fsapi_sync_fs(sb, 1); + atomic_set(&sb_dfr->stat, DFR_SB_STAT_REQ); + complete_all(&sbi->dfr_complete); + } else if (((spent < 0) || (spent > DFR_DEFAULT_TIMEOUT)) && + (atomic_read(&(sbi->dfr_info.stat)) == DFR_SB_STAT_IDLE)) { + uevent = fsapi_dfr_check_dfr_required(sb, &total, &clean, &full); + time = jiffies; + } + __unlock_super(sb); + + if (uevent) { + kobject_uevent(&SDFAT_SB(sb)->sb_kobj, KOBJ_CHANGE); + dfr_debug("uevent for defrag_daemon, total_au %d, " + "clean_au %d, full_au %d", total, clean, full); + } +#endif +} + +static int sdfat_sync_fs(struct super_block *sb, int wait) +{ + int err = 0; + + /* If there are some dirty buffers in the bdev inode */ + if (__is_sb_dirty(sb)) { + __lock_super(sb); + __set_sb_clean(sb); + + __dfr_update_fat_next(sb); + + err = fsapi_sync_fs(sb, 1); + +#if (defined(CONFIG_SDFAT_DFR) && defined(CONFIG_SDFAT_DFR_DEBUG)) + /* SPO test */ + fsapi_dfr_spo_test(sb, DFR_SPO_FAT_NEXT, __func__); +#endif + + __unlock_super(sb); + } + + __dfr_update_fat_prev(sb, wait); + + return err; +} + +static int sdfat_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + /* + * patch 1.2.2 : + * fixed the slow-call problem because of volume-lock contention. + */ + struct super_block *sb = dentry->d_sb; + u64 id = huge_encode_dev(sb->s_bdev->bd_dev); + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + VOL_INFO_T info; + + /* fsapi_statfs will try to get a volume lock if needed */ + if (fsapi_statfs(sb, &info)) + return -EIO; + + if (fsi->prev_eio) + sdfat_msg(sb, KERN_INFO, "called statfs with previous" + " I/O error(0x%02X).", fsi->prev_eio); + + buf->f_type = sb->s_magic; + buf->f_bsize = info.ClusterSize; + buf->f_blocks = info.NumClusters; + buf->f_bfree = info.FreeClusters; + buf->f_bavail = info.FreeClusters; + buf->f_fsid.val[0] = (u32)id; + buf->f_fsid.val[1] = (u32)(id >> 32); + buf->f_namelen = 260; + + return 0; +} + +static int sdfat_remount(struct super_block *sb, int *flags, char *data) +{ + unsigned long prev_sb_flags; + char *orig_data = kstrdup(data, GFP_KERNEL); + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + + *flags |= MS_NODIRATIME; + + prev_sb_flags = sb->s_flags; + + sdfat_remount_syncfs(sb); + + fsapi_set_vol_flags(sb, VOL_CLEAN, 1); + + sdfat_log_msg(sb, KERN_INFO, "re-mounted(%s->%s), eio=0x%x, Opts: %s", + (prev_sb_flags & MS_RDONLY) ? "ro" : "rw", + (*flags & MS_RDONLY) ? "ro" : "rw", + fsi->prev_eio, orig_data); + kfree(orig_data); + return 0; +} + +static int __sdfat_show_options(struct seq_file *m, struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct sdfat_mount_options *opts = &sbi->options; + FS_INFO_T *fsi = &(sbi->fsi); + + /* Show partition info */ + seq_printf(m, ",fs=%s", sdfat_get_vol_type_str(fsi->vol_type)); + if (fsi->prev_eio) + seq_printf(m, ",eio=0x%x", fsi->prev_eio); + if (!uid_eq(opts->fs_uid, GLOBAL_ROOT_UID)) + seq_printf(m, ",uid=%u", + from_kuid_munged(&init_user_ns, opts->fs_uid)); + if (!gid_eq(opts->fs_gid, GLOBAL_ROOT_GID)) + seq_printf(m, ",gid=%u", + from_kgid_munged(&init_user_ns, opts->fs_gid)); + seq_printf(m, ",fmask=%04o", opts->fs_fmask); + seq_printf(m, ",dmask=%04o", opts->fs_dmask); + if (opts->allow_utime) + seq_printf(m, ",allow_utime=%04o", opts->allow_utime); + if (sbi->nls_disk) + seq_printf(m, ",codepage=%s", sbi->nls_disk->charset); + if (sbi->nls_io) + seq_printf(m, ",iocharset=%s", sbi->nls_io->charset); + if (opts->utf8) + seq_puts(m, ",utf8"); + if (sbi->fsi.vol_type != EXFAT) + seq_puts(m, ",shortname=winnt"); + seq_printf(m, ",namecase=%u", opts->casesensitive); + if (opts->tz_utc) + seq_puts(m, ",tz=UTC"); + if (opts->improved_allocation & SDFAT_ALLOC_DELAY) + seq_puts(m, ",delay"); + if (opts->improved_allocation & SDFAT_ALLOC_SMART) + seq_printf(m, ",smart,ausize=%u", opts->amap_opt.sect_per_au); + if (opts->defrag) + seq_puts(m, ",defrag"); + if (opts->adj_hidsect) + seq_puts(m, ",adj_hid"); + if (opts->adj_req) + seq_puts(m, ",adj_req"); + seq_printf(m, ",symlink=%u", opts->symlink); + seq_printf(m, ",bps=%ld", sb->s_blocksize); + if (opts->errors == SDFAT_ERRORS_CONT) + seq_puts(m, ",errors=continue"); + else if (opts->errors == SDFAT_ERRORS_PANIC) + seq_puts(m, ",errors=panic"); + else + seq_puts(m, ",errors=remount-ro"); + if (opts->discard) + seq_puts(m, ",discard"); + + return 0; +} + +static const struct super_operations sdfat_sops = { + .alloc_inode = sdfat_alloc_inode, + .destroy_inode = sdfat_destroy_inode, + .write_inode = sdfat_write_inode, + .evict_inode = sdfat_evict_inode, + .put_super = sdfat_put_super, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) + .write_super = sdfat_write_super, +#endif + .sync_fs = sdfat_sync_fs, + .statfs = sdfat_statfs, + .remount_fs = sdfat_remount, + .show_options = sdfat_show_options, +}; + +/*======================================================================*/ +/* SYSFS Operations */ +/*======================================================================*/ +#define SDFAT_ATTR(name, mode, show, store) \ +static struct sdfat_attr sdfat_attr_##name = __ATTR(name, mode, show, store) + +struct sdfat_attr { + struct attribute attr; + ssize_t (*show)(struct sdfat_sb_info *, char *); + ssize_t (*store)(struct sdfat_sb_info *, const char *, size_t); +}; + +static ssize_t sdfat_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct sdfat_sb_info *sbi = container_of(kobj, struct sdfat_sb_info, sb_kobj); + struct sdfat_attr *a = container_of(attr, struct sdfat_attr, attr); + + return a->show ? a->show(sbi, buf) : 0; +} + +static ssize_t sdfat_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t len) +{ + struct sdfat_sb_info *sbi = container_of(kobj, struct sdfat_sb_info, sb_kobj); + struct sdfat_attr *a = container_of(attr, struct sdfat_attr, attr); + + return a->store ? a->store(sbi, buf, len) : len; +} + +static const struct sysfs_ops sdfat_attr_ops = { + .show = sdfat_attr_show, + .store = sdfat_attr_store, +}; + + +static ssize_t type_show(struct sdfat_sb_info *sbi, char *buf) +{ + FS_INFO_T *fsi = &(sbi->fsi); + + return snprintf(buf, PAGE_SIZE, "%s\n", sdfat_get_vol_type_str(fsi->vol_type)); +} +SDFAT_ATTR(type, 0444, type_show, NULL); + +static ssize_t eio_show(struct sdfat_sb_info *sbi, char *buf) +{ + FS_INFO_T *fsi = &(sbi->fsi); + + return snprintf(buf, PAGE_SIZE, "0x%x\n", fsi->prev_eio); +} +SDFAT_ATTR(eio, 0444, eio_show, NULL); + +static ssize_t fratio_show(struct sdfat_sb_info *sbi, char *buf) +{ + unsigned int n_total_au = 0; + unsigned int n_clean_au = 0; + unsigned int n_full_au = 0; + unsigned int n_dirty_au = 0; + unsigned int fr = 0; + + n_total_au = fsapi_get_au_stat(sbi->host_sb, VOL_AU_STAT_TOTAL); + n_clean_au = fsapi_get_au_stat(sbi->host_sb, VOL_AU_STAT_CLEAN); + n_full_au = fsapi_get_au_stat(sbi->host_sb, VOL_AU_STAT_FULL); + n_dirty_au = n_total_au - (n_full_au + n_clean_au); + + if (!n_dirty_au) + fr = 0; + else if (!n_clean_au) + fr = 100; + else + fr = (n_dirty_au * 100) / (n_clean_au + n_dirty_au); + + return snprintf(buf, PAGE_SIZE, "%u\n", fr); +} +SDFAT_ATTR(fratio, 0444, fratio_show, NULL); + +static ssize_t totalau_show(struct sdfat_sb_info *sbi, char *buf) +{ + unsigned int n_au = 0; + + n_au = fsapi_get_au_stat(sbi->host_sb, VOL_AU_STAT_TOTAL); + return snprintf(buf, PAGE_SIZE, "%u\n", n_au); +} +SDFAT_ATTR(totalau, 0444, totalau_show, NULL); + +static ssize_t cleanau_show(struct sdfat_sb_info *sbi, char *buf) +{ + unsigned int n_clean_au = 0; + + n_clean_au = fsapi_get_au_stat(sbi->host_sb, VOL_AU_STAT_CLEAN); + return snprintf(buf, PAGE_SIZE, "%u\n", n_clean_au); +} +SDFAT_ATTR(cleanau, 0444, cleanau_show, NULL); + +static ssize_t fullau_show(struct sdfat_sb_info *sbi, char *buf) +{ + unsigned int n_full_au = 0; + + n_full_au = fsapi_get_au_stat(sbi->host_sb, VOL_AU_STAT_FULL); + return snprintf(buf, PAGE_SIZE, "%u\n", n_full_au); +} +SDFAT_ATTR(fullau, 0444, fullau_show, NULL); + +static struct attribute *sdfat_attrs[] = { + &sdfat_attr_type.attr, + &sdfat_attr_eio.attr, + &sdfat_attr_fratio.attr, + &sdfat_attr_totalau.attr, + &sdfat_attr_cleanau.attr, + &sdfat_attr_fullau.attr, + NULL, +}; + +static struct kobj_type sdfat_ktype = { + .default_attrs = sdfat_attrs, + .sysfs_ops = &sdfat_attr_ops, +}; + +static ssize_t version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "FS Version %s\n", SDFAT_VERSION); +} + +static struct kobj_attribute version_attr = __ATTR_RO(version); + +static struct attribute *attributes[] = { + &version_attr.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attributes, +}; + +/*======================================================================*/ +/* Super Block Read Operations */ +/*======================================================================*/ + +enum { + Opt_uid, + Opt_gid, + Opt_umask, + Opt_dmask, + Opt_fmask, + Opt_allow_utime, + Opt_codepage, + Opt_charset, + Opt_utf8, + Opt_namecase, + Opt_tz_utc, + Opt_adj_hidsect, + Opt_delay, + Opt_smart, + Opt_ausize, + Opt_packing, + Opt_defrag, + Opt_symlink, + Opt_debug, + Opt_err_cont, + Opt_err_panic, + Opt_err_ro, + Opt_err, + Opt_discard, + Opt_fs, + Opt_adj_req, +#ifdef CONFIG_SDFAT_USE_FOR_VFAT + Opt_shortname_lower, + Opt_shortname_win95, + Opt_shortname_winnt, + Opt_shortname_mixed, +#endif /* CONFIG_SDFAT_USE_FOR_VFAT */ +}; + +static const match_table_t sdfat_tokens = { + {Opt_uid, "uid=%u"}, + {Opt_gid, "gid=%u"}, + {Opt_umask, "umask=%o"}, + {Opt_dmask, "dmask=%o"}, + {Opt_fmask, "fmask=%o"}, + {Opt_allow_utime, "allow_utime=%o"}, + {Opt_codepage, "codepage=%u"}, + {Opt_charset, "iocharset=%s"}, + {Opt_utf8, "utf8"}, + {Opt_namecase, "namecase=%u"}, + {Opt_tz_utc, "tz=UTC"}, + {Opt_adj_hidsect, "adj_hid"}, + {Opt_delay, "delay"}, + {Opt_smart, "smart"}, + {Opt_ausize, "ausize=%u"}, + {Opt_packing, "packing=%u"}, + {Opt_defrag, "defrag"}, + {Opt_symlink, "symlink=%u"}, + {Opt_debug, "debug"}, + {Opt_err_cont, "errors=continue"}, + {Opt_err_panic, "errors=panic"}, + {Opt_err_ro, "errors=remount-ro"}, + {Opt_discard, "discard"}, + {Opt_fs, "fs=%s"}, + {Opt_adj_req, "adj_req"}, +#ifdef CONFIG_SDFAT_USE_FOR_VFAT + {Opt_shortname_lower, "shortname=lower"}, + {Opt_shortname_win95, "shortname=win95"}, + {Opt_shortname_winnt, "shortname=winnt"}, + {Opt_shortname_mixed, "shortname=mixed"}, +#endif /* CONFIG_SDFAT_USE_FOR_VFAT */ + {Opt_err, NULL} +}; + +static int parse_options(struct super_block *sb, char *options, int silent, + int *debug, struct sdfat_mount_options *opts) +{ + char *p; + substring_t args[MAX_OPT_ARGS]; + int option, i; + char *tmpstr; + + opts->fs_uid = current_uid(); + opts->fs_gid = current_gid(); + opts->fs_fmask = opts->fs_dmask = current->fs->umask; + opts->allow_utime = (unsigned short) -1; + opts->codepage = sdfat_default_codepage; + opts->iocharset = sdfat_default_iocharset; + opts->casesensitive = 0; + opts->utf8 = 0; + opts->adj_hidsect = 0; + opts->tz_utc = 0; + opts->improved_allocation = 0; + opts->amap_opt.pack_ratio = 0; // Default packing + opts->amap_opt.sect_per_au = 0; + opts->amap_opt.misaligned_sect = 0; + opts->symlink = 0; + opts->errors = SDFAT_ERRORS_RO; + opts->discard = 0; + *debug = 0; + + if (!options) + goto out; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + + if (!*p) + continue; + token = match_token(p, sdfat_tokens, args); + switch (token) { + case Opt_uid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_uid = make_kuid(current_user_ns(), option); + break; + case Opt_gid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_gid = make_kgid(current_user_ns(), option); + break; + case Opt_umask: + case Opt_dmask: + case Opt_fmask: + if (match_octal(&args[0], &option)) + return 0; + if (token != Opt_dmask) + opts->fs_fmask = option; + if (token != Opt_fmask) + opts->fs_dmask = option; + break; + case Opt_allow_utime: + if (match_octal(&args[0], &option)) + return 0; + opts->allow_utime = option & (S_IWGRP | S_IWOTH); + break; + case Opt_codepage: + if (match_int(&args[0], &option)) + return 0; + opts->codepage = option; + break; + case Opt_charset: + if (opts->iocharset != sdfat_default_iocharset) + kfree(opts->iocharset); + tmpstr = match_strdup(&args[0]); + if (!tmpstr) + return -ENOMEM; + opts->iocharset = tmpstr; + break; + case Opt_namecase: + if (match_int(&args[0], &option)) + return 0; + opts->casesensitive = (option > 0) ? 1:0; + break; + case Opt_utf8: + opts->utf8 = 1; + break; + case Opt_adj_hidsect: + opts->adj_hidsect = 1; + break; + case Opt_tz_utc: + opts->tz_utc = 1; + break; + case Opt_symlink: + if (match_int(&args[0], &option)) + return 0; + opts->symlink = option > 0 ? 1 : 0; + break; + case Opt_delay: + opts->improved_allocation |= SDFAT_ALLOC_DELAY; + break; + case Opt_smart: + opts->improved_allocation |= SDFAT_ALLOC_SMART; + break; + case Opt_ausize: + if (match_int(&args[0], &option)) + return -EINVAL; + if (!is_power_of_2(option)) + return -EINVAL; + opts->amap_opt.sect_per_au = option; + IMSG("set AU size by option : %u sectors\n", option); + break; + case Opt_packing: + if (match_int(&args[0], &option)) + return 0; + opts->amap_opt.pack_ratio = option; + break; + case Opt_defrag: +#ifdef CONFIG_SDFAT_DFR + opts->defrag = 1; +#else + IMSG("defragmentation config is not enabled. ignore\n"); +#endif + break; + case Opt_err_cont: + opts->errors = SDFAT_ERRORS_CONT; + break; + case Opt_err_panic: + opts->errors = SDFAT_ERRORS_PANIC; + break; + case Opt_err_ro: + opts->errors = SDFAT_ERRORS_RO; + break; + case Opt_debug: + *debug = 1; + break; + case Opt_discard: + opts->discard = 1; + break; + case Opt_fs: + tmpstr = match_strdup(&args[0]); + if (!tmpstr) + return -ENOMEM; + for (i = 0; i < FS_TYPE_MAX; i++) { + if (!strcmp(tmpstr, FS_TYPE_STR[i])) { + opts->fs_type = (unsigned char)i; + sdfat_log_msg(sb, KERN_ERR, + "set fs-type by option : %s", + FS_TYPE_STR[i]); + break; + } + } + kfree(tmpstr); + if (i == FS_TYPE_MAX) { + sdfat_log_msg(sb, KERN_ERR, + "invalid fs-type, " + "only allow auto, exfat, vfat"); + return -EINVAL; + } + break; + case Opt_adj_req: +#ifdef CONFIG_SDFAT_ALIGNED_MPAGE_WRITE + opts->adj_req = 1; +#else + IMSG("adjust request config is not enabled. ignore\n"); +#endif + break; +#ifdef CONFIG_SDFAT_USE_FOR_VFAT + case Opt_shortname_lower: + case Opt_shortname_win95: + case Opt_shortname_mixed: + pr_warn("[SDFAT] DRAGONS AHEAD! sdFAT only understands \"shortname=winnt\"!\n"); + case Opt_shortname_winnt: + break; +#endif /* CONFIG_SDFAT_USE_FOR_VFAT */ + default: + if (!silent) { + sdfat_msg(sb, KERN_ERR, + "unrecognized mount option \"%s\" " + "or missing value", p); + } + return -EINVAL; + } + } + +out: + if (opts->allow_utime == (unsigned short) -1) + opts->allow_utime = ~opts->fs_dmask & (S_IWGRP | S_IWOTH); + + if (opts->utf8 && strcmp(opts->iocharset, sdfat_iocharset_with_utf8)) { + sdfat_msg(sb, KERN_WARNING, + "utf8 enabled, \"iocharset=%s\" is recommended", + sdfat_iocharset_with_utf8); + } + + if (opts->discard) { + struct request_queue *q = bdev_get_queue(sb->s_bdev); + + if (!blk_queue_discard(q)) + sdfat_msg(sb, KERN_WARNING, + "mounting with \"discard\" option, but " + "the device does not support discard"); + opts->discard = 0; + } + + return 0; +} + +static void sdfat_hash_init(struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + int i; + + spin_lock_init(&sbi->inode_hash_lock); + for (i = 0; i < SDFAT_HASH_SIZE; i++) + INIT_HLIST_HEAD(&sbi->inode_hashtable[i]); +} + +static int sdfat_read_root(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + DIR_ENTRY_T info; + + SDFAT_I(inode)->fid.dir.dir = fsi->root_dir; + SDFAT_I(inode)->fid.dir.flags = 0x01; + SDFAT_I(inode)->fid.entry = -1; + SDFAT_I(inode)->fid.start_clu = fsi->root_dir; + SDFAT_I(inode)->fid.flags = 0x01; + SDFAT_I(inode)->fid.type = TYPE_DIR; + SDFAT_I(inode)->fid.version = 0; + SDFAT_I(inode)->fid.rwoffset = 0; + SDFAT_I(inode)->fid.hint_bmap.off = CLUS_EOF; + SDFAT_I(inode)->fid.hint_stat.eidx = 0; + SDFAT_I(inode)->fid.hint_stat.clu = fsi->root_dir; + SDFAT_I(inode)->fid.hint_femp.eidx = -1; + + SDFAT_I(inode)->target = NULL; + + if (fsapi_read_inode(inode, &info) < 0) + return -EIO; + + inode->i_uid = sbi->options.fs_uid; + inode->i_gid = sbi->options.fs_gid; + inode->i_version++; + inode->i_generation = 0; + inode->i_mode = sdfat_make_mode(sbi, ATTR_SUBDIR, S_IRWXUGO); + inode->i_op = &sdfat_dir_inode_operations; + inode->i_fop = &sdfat_dir_operations; + + i_size_write(inode, info.Size); + SDFAT_I(inode)->fid.size = info.Size; + inode->i_blocks = ((i_size_read(inode) + (fsi->cluster_size - 1)) + & ~((loff_t)fsi->cluster_size - 1)) >> inode->i_blkbits; + SDFAT_I(inode)->i_pos = ((loff_t) fsi->root_dir << 32) | 0xffffffff; + SDFAT_I(inode)->i_size_aligned = i_size_read(inode); + SDFAT_I(inode)->i_size_ondisk = i_size_read(inode); + + sdfat_save_attr(inode, ATTR_SUBDIR); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + set_nlink(inode, info.NumSubdirs + 2); + return 0; +} + + + +static void setup_dops(struct super_block *sb) +{ + if (SDFAT_SB(sb)->options.casesensitive == 0) + sb->s_d_op = &sdfat_ci_dentry_ops; + else + sb->s_d_op = &sdfat_dentry_ops; +} + +static int sdfat_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root_inode = NULL; + struct sdfat_sb_info *sbi; + int debug; + int err; + char buf[50]; + struct block_device *bdev = sb->s_bdev; + dev_t bd_dev = bdev ? bdev->bd_dev : 0; + + sdfat_log_msg(sb, KERN_INFO, "trying to mount..."); + + /* + * GFP_KERNEL is ok here, because while we do hold the + * supeblock lock, memory pressure can't call back into + * the filesystem, since we're only just about to mount + * it and have no inodes etc active! + */ + sbi = kzalloc(sizeof(struct sdfat_sb_info), GFP_KERNEL); + if (!sbi) { + sdfat_log_msg(sb, KERN_INFO, + "trying to alloc sbi with vzalloc()"); + sbi = vzalloc(sizeof(struct sdfat_sb_info)); + if (!sbi) { + sdfat_log_msg(sb, KERN_ERR, "failed to mount! (ENOMEM)"); + return -ENOMEM; + } + sbi->use_vmalloc = 1; + } + + mutex_init(&sbi->s_vlock); + sb->s_fs_info = sbi; + sb->s_flags |= MS_NODIRATIME; + sb->s_magic = SDFAT_SUPER_MAGIC; + sb->s_op = &sdfat_sops; + ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, + DEFAULT_RATELIMIT_BURST); + err = parse_options(sb, data, silent, &debug, &sbi->options); + if (err) { + sdfat_log_msg(sb, KERN_ERR, "failed to parse options"); + goto failed_mount; + } + + setup_sdfat_xattr_handler(sb); + setup_sdfat_sync_super_wq(sb); + setup_dops(sb); + + err = fsapi_mount(sb); + if (err) { + sdfat_log_msg(sb, KERN_ERR, "failed to recognize fat type"); + goto failed_mount; + } + + /* set up enough so that it can read an inode */ + sdfat_hash_init(sb); + + /* + * The low byte of FAT's first entry must have same value with + * media-field. But in real world, too many devices is + * writing wrong value. So, removed that validity check. + * + * if (FAT_FIRST_ENT(sb, media) != first) + */ + + err = -EINVAL; + sprintf(buf, "cp%d", sbi->options.codepage); + sbi->nls_disk = load_nls(buf); + if (!sbi->nls_disk) { + sdfat_log_msg(sb, KERN_ERR, "codepage %s not found", buf); + goto failed_mount2; + } + + sbi->nls_io = load_nls(sbi->options.iocharset); + if (!sbi->nls_io) { + sdfat_log_msg(sb, KERN_ERR, "IO charset %s not found", + sbi->options.iocharset); + goto failed_mount2; + } + + err = __alloc_dfr_mem_if_required(sb); + if (err) { + sdfat_log_msg(sb, KERN_ERR, "failed to initialize a memory for " + "defragmentation"); + goto failed_mount3; + } + + err = -ENOMEM; + root_inode = new_inode(sb); + if (!root_inode) { + sdfat_log_msg(sb, KERN_ERR, "failed to allocate root inode."); + goto failed_mount3; + } + + root_inode->i_ino = SDFAT_ROOT_INO; + root_inode->i_version = 1; + + err = sdfat_read_root(root_inode); + if (err) { + sdfat_log_msg(sb, KERN_ERR, "failed to initialize root inode."); + goto failed_mount3; + } + + sdfat_attach(root_inode, SDFAT_I(root_inode)->i_pos); + insert_inode_hash(root_inode); + + err = -ENOMEM; + sb->s_root = __d_make_root(root_inode); + if (!sb->s_root) { + sdfat_msg(sb, KERN_ERR, "failed to get the root dentry"); + goto failed_mount3; + } + + /* + * Initialize filesystem attributes (for sysfs) + * ex: /sys/fs/sdfat/mmcblk1[179:17] + */ + sbi->sb_kobj.kset = sdfat_kset; + err = kobject_init_and_add(&sbi->sb_kobj, &sdfat_ktype, NULL, + "%s[%d:%d]", sb->s_id, MAJOR(bd_dev), MINOR(bd_dev)); + if (err) { + sdfat_msg(sb, KERN_ERR, "Unable to create sdfat attributes for" + " %s[%d:%d](%d)", sb->s_id, + MAJOR(bd_dev), MINOR(bd_dev), err); + goto failed_mount3; + } + + sdfat_log_msg(sb, KERN_INFO, "mounted successfully!"); + /* FOR BIGDATA */ + sdfat_statistics_set_mnt(&sbi->fsi); + sdfat_statistics_set_vol_size(sb); + return 0; + +failed_mount3: + __free_dfr_mem_if_required(sb); +failed_mount2: + fsapi_umount(sb); +failed_mount: + sdfat_log_msg(sb, KERN_INFO, "failed to mount! (%d)", err); + + if (root_inode) + iput(root_inode); + sb->s_root = NULL; + + if (sbi->nls_io) + unload_nls(sbi->nls_io); + if (sbi->nls_disk) + unload_nls(sbi->nls_disk); + if (sbi->options.iocharset != sdfat_default_iocharset) + kfree(sbi->options.iocharset); + sb->s_fs_info = NULL; + if (!sbi->use_vmalloc) + kfree(sbi); + else + vfree(sbi); + return err; +} + +static struct dentry *sdfat_fs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) { + return mount_bdev(fs_type, flags, dev_name, data, sdfat_fill_super); +} + +static void init_once(void *foo) +{ + struct sdfat_inode_info *ei = (struct sdfat_inode_info *)foo; + + INIT_HLIST_NODE(&ei->i_hash_fat); + inode_init_once(&ei->vfs_inode); +} + +static int __init sdfat_init_inodecache(void) +{ + sdfat_inode_cachep = kmem_cache_create("sdfat_inode_cache", + sizeof(struct sdfat_inode_info), + 0, (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD), + init_once); + if (!sdfat_inode_cachep) + return -ENOMEM; + return 0; +} + +static void sdfat_destroy_inodecache(void) +{ + /* + * Make sure all delayed rcu free inodes are flushed before we + * destroy cache. + */ + rcu_barrier(); + kmem_cache_destroy(sdfat_inode_cachep); +} + +#ifdef CONFIG_SDFAT_DBG_IOCTL +static void sdfat_debug_kill_sb(struct super_block *sb) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(sb); + struct block_device *bdev = sb->s_bdev; + + long flags; + + if (sbi) { + flags = sbi->debug_flags; + + if (flags & SDFAT_DEBUGFLAGS_INVALID_UMOUNT) { + /* invalidate_bdev drops all device cache include dirty. + * we use this to simulate device removal + */ + fsapi_cache_release(sb); + invalidate_bdev(bdev); + } + } + + kill_block_super(sb); +} +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + +static struct file_system_type sdfat_fs_type = { + .owner = THIS_MODULE, + .name = "sdfat", + .mount = sdfat_fs_mount, +#ifdef CONFIG_SDFAT_DBG_IOCTL + .kill_sb = sdfat_debug_kill_sb, +#else + .kill_sb = kill_block_super, +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + .fs_flags = FS_REQUIRES_DEV, +}; + +#ifdef CONFIG_SDFAT_USE_FOR_EXFAT +static struct file_system_type exfat_fs_type = { + .owner = THIS_MODULE, + .name = "exfat", + .mount = sdfat_fs_mount, +#ifdef CONFIG_SDFAT_DBG_IOCTL + .kill_sb = sdfat_debug_kill_sb, +#else + .kill_sb = kill_block_super, +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + .fs_flags = FS_REQUIRES_DEV, +}; +#endif /* CONFIG_SDFAT_USE_FOR_EXFAT */ + +#ifdef CONFIG_SDFAT_USE_FOR_VFAT +static struct file_system_type vfat_fs_type = { + .owner = THIS_MODULE, + .name = "vfat", + .mount = sdfat_fs_mount, +#ifdef CONFIG_SDFAT_DBG_IOCTL + .kill_sb = sdfat_debug_kill_sb, +#else + .kill_sb = kill_block_super, +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + .fs_flags = FS_REQUIRES_DEV, +}; +#endif /* CONFIG_SDFAT_USE_FOR_VFAT */ + +static int __init init_sdfat_fs(void) +{ + int err; + + sdfat_log_version(); + err = fsapi_init(); + if (err) + goto error; + + sdfat_kset = kset_create_and_add("sdfat", NULL, fs_kobj); + if (!sdfat_kset) { + pr_err("[SDFAT] failed to create fs_kobj\n"); + err = -ENOMEM; + goto error; + } + + err = sysfs_create_group(&sdfat_kset->kobj, &attr_group); + if (err) { + pr_err("[SDFAT] failed to create sdfat version attributes\n"); + goto error; + } + + err = sdfat_statistics_init(sdfat_kset); + if (err) + goto error; + + err = sdfat_init_inodecache(); + if (err) { + pr_err("[SDFAT] failed to initialize inode cache\n"); + goto error; + } + + err = register_filesystem(&sdfat_fs_type); + if (err) { + pr_err("[SDFAT] failed to register filesystem\n"); + goto error; + } + +#ifdef CONFIG_SDFAT_USE_FOR_EXFAT + err = register_filesystem(&exfat_fs_type); + if (err) { + pr_err("[SDFAT] failed to register for exfat filesystem\n"); + goto error; + } +#endif /* CONFIG_SDFAT_USE_FOR_EXFAT */ + +#ifdef CONFIG_SDFAT_USE_FOR_VFAT + err = register_filesystem(&vfat_fs_type); + if (err) { + pr_err("[SDFAT] failed to register for vfat filesystem\n"); + goto error; + } +#endif /* CONFIG_SDFAT_USE_FOR_VFAT */ + + return 0; +error: + sdfat_statistics_uninit(); + + if (sdfat_kset) { + sysfs_remove_group(&sdfat_kset->kobj, &attr_group); + kset_unregister(sdfat_kset); + sdfat_kset = NULL; + } + + sdfat_destroy_inodecache(); + fsapi_shutdown(); + + pr_err("[SDFAT] failed to initialize FS driver(err:%d)\n", err); + return err; +} + +static void __exit exit_sdfat_fs(void) +{ + sdfat_statistics_uninit(); + + if (sdfat_kset) { + sysfs_remove_group(&sdfat_kset->kobj, &attr_group); + kset_unregister(sdfat_kset); + sdfat_kset = NULL; + } + + sdfat_destroy_inodecache(); + unregister_filesystem(&sdfat_fs_type); +#ifdef CONFIG_SDFAT_USE_FOR_EXFAT + unregister_filesystem(&exfat_fs_type); +#endif /* CONFIG_SDFAT_USE_FOR_EXFAT */ +#ifdef CONFIG_SDFAT_USE_FOR_VFAT + unregister_filesystem(&vfat_fs_type); +#endif /* CONFIG_SDFAT_USE_FOR_VFAT */ + fsapi_shutdown(); +} + +module_init(init_sdfat_fs); +module_exit(exit_sdfat_fs); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("FAT/exFAT filesystem support"); +MODULE_AUTHOR("Samsung Electronics Co., Ltd."); diff --git a/fs/sdfat/sdfat.h b/fs/sdfat/sdfat.h new file mode 100644 index 0000000000000..6f764210fe4d7 --- /dev/null +++ b/fs/sdfat/sdfat.h @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _SDFAT_H +#define _SDFAT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "api.h" + +#ifdef CONFIG_SDFAT_DFR +#include "dfr.h" +#endif + +/* + * sdfat error flags + */ +#define SDFAT_ERRORS_CONT (1) /* ignore error and continue */ +#define SDFAT_ERRORS_PANIC (2) /* panic on error */ +#define SDFAT_ERRORS_RO (3) /* remount r/o on error */ + +/* + * sdfat allocator flags + */ +#define SDFAT_ALLOC_DELAY (1) /* Delayed allocation */ +#define SDFAT_ALLOC_SMART (2) /* Smart allocation */ + +/* + * sdfat allocator destination for smart allocation + */ +#define ALLOC_NOWHERE (0) +#define ALLOC_COLD (1) +#define ALLOC_HOT (16) +#define ALLOC_COLD_ALIGNED (1) +#define ALLOC_COLD_PACKING (2) +#define ALLOC_COLD_SEQ (4) + +/* + * sdfat nls lossy flag + */ +#define NLS_NAME_NO_LOSSY (0x00) /* no lossy */ +#define NLS_NAME_LOSSY (0x01) /* just detected incorrect filename(s) */ +#define NLS_NAME_OVERLEN (0x02) /* the length is over than its limit */ + +/* + * sdfat common MACRO + */ +#define CLUSTER_16(x) ((u16)((x) & 0xFFFFU)) +#define CLUSTER_32(x) ((u32)((x) & 0xFFFFFFFFU)) +#define CLUS_EOF CLUSTER_32(~0) +#define CLUS_BAD (0xFFFFFFF7U) +#define CLUS_FREE (0) +#define CLUS_BASE (2) +#define IS_CLUS_EOF(x) ((x) == CLUS_EOF) +#define IS_CLUS_BAD(x) ((x) == CLUS_BAD) +#define IS_CLUS_FREE(x) ((x) == CLUS_FREE) +#define IS_LAST_SECT_IN_CLUS(fsi, sec) \ + ((((sec) - (fsi)->data_start_sector + 1) \ + & ((1 << (fsi)->sect_per_clus_bits) - 1)) == 0) + +#define CLUS_TO_SECT(fsi, x) \ + ((((unsigned long long)(x) - CLUS_BASE) << (fsi)->sect_per_clus_bits) + (fsi)->data_start_sector) + +#define SECT_TO_CLUS(fsi, sec) \ + ((u32)((((sec) - (fsi)->data_start_sector) >> (fsi)->sect_per_clus_bits) + CLUS_BASE)) + +/* variables defined at sdfat.c */ +extern const char *FS_TYPE_STR[]; + +enum { + FS_TYPE_AUTO, + FS_TYPE_EXFAT, + FS_TYPE_VFAT, + FS_TYPE_MAX +}; + +/* + * sdfat mount in-memory data + */ +struct sdfat_mount_options { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + kuid_t fs_uid; + kgid_t fs_gid; +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) */ + uid_t fs_uid; + gid_t fs_gid; +#endif + unsigned short fs_fmask; + unsigned short fs_dmask; + unsigned short allow_utime; /* permission for setting the [am]time */ + unsigned short codepage; /* codepage for shortname conversions */ + char *iocharset; /* charset for filename input/display */ + struct { + unsigned int pack_ratio; + unsigned int sect_per_au; + unsigned int misaligned_sect; + } amap_opt; /* AMAP-related options (see amap.c) */ + + unsigned char utf8; + unsigned char casesensitive; + unsigned char adj_hidsect; + unsigned char tz_utc; + unsigned char improved_allocation; + unsigned char defrag; + unsigned char symlink; /* support symlink operation */ + unsigned char errors; /* on error: continue, panic, remount-ro */ + unsigned char discard; /* flag on if -o dicard specified and device support discard() */ + unsigned char fs_type; /* fs_type that user specified */ + unsigned short adj_req; /* support aligned mpage write */ +}; + +#define SDFAT_HASH_BITS 8 +#define SDFAT_HASH_SIZE (1UL << SDFAT_HASH_BITS) + +/* + * SDFAT file system superblock in-memory data + */ +struct sdfat_sb_info { + FS_INFO_T fsi; /* private filesystem info */ + + struct mutex s_vlock; /* volume lock */ + int use_vmalloc; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) + int s_dirt; + struct mutex s_lock; /* superblock lock */ + int write_super_queued; /* Write_super work is pending? */ + struct delayed_work write_super_work; /* Work_queue data structrue for write_super() */ + spinlock_t work_lock; /* Lock for WQ */ +#endif + struct super_block *host_sb; /* sb pointer */ + struct sdfat_mount_options options; + struct nls_table *nls_disk; /* Codepage used on disk */ + struct nls_table *nls_io; /* Charset used for input and display */ + struct ratelimit_state ratelimit; + + spinlock_t inode_hash_lock; + struct hlist_head inode_hashtable[SDFAT_HASH_SIZE]; + struct kobject sb_kobj; +#ifdef CONFIG_SDFAT_DBG_IOCTL + long debug_flags; +#endif /* CONFIG_SDFAT_DBG_IOCTL */ + +#ifdef CONFIG_SDFAT_DFR + struct defrag_info dfr_info; + struct completion dfr_complete; + unsigned int *dfr_new_clus; + int dfr_new_idx; + unsigned int *dfr_page_wb; + void **dfr_pagep; + unsigned int dfr_hint_clus; + unsigned int dfr_hint_idx; + int dfr_reserved_clus; + +#ifdef CONFIG_SDFAT_DFR_DEBUG + int dfr_spo_flag; +#endif /* CONFIG_SDFAT_DFR_DEBUG */ + +#endif /* CONFIG_SDFAT_DFR */ + +#ifdef CONFIG_SDFAT_TRACE_IO + /* Statistics for allocator */ + unsigned int stat_n_pages_written; /* # of written pages in total */ + unsigned int stat_n_pages_added; /* # of added blocks in total */ + unsigned int stat_n_bdev_pages_written; /* # of written pages owned by bdev inode */ + unsigned int stat_n_pages_confused; +#endif + atomic_t stat_n_pages_queued; /* # of pages in the request queue (approx.) */ +}; + +/* + * SDFAT file system inode in-memory data + */ +struct sdfat_inode_info { + FILE_ID_T fid; + char *target; + /* NOTE: i_size_ondisk is 64bits, so must hold ->inode_lock to access */ + loff_t i_size_ondisk; /* physically allocated size */ + loff_t i_size_aligned; /* block-aligned i_size (used in cont_write_begin) */ + loff_t i_pos; /* on-disk position of directory entry or 0 */ + struct hlist_node i_hash_fat; /* hash by i_location */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + struct rw_semaphore truncate_lock; /* protect bmap against truncate */ +#endif +#ifdef CONFIG_SDFAT_DFR + struct defrag_info dfr_info; +#endif + struct inode vfs_inode; +}; + +/* + * FIXME : needs on-disk-slot in-memory data + */ + +/* static inline functons */ +static inline const char *sdfat_get_vol_type_str(unsigned int type) +{ + if (type == EXFAT) + return "exfat"; + else if (type == FAT32) + return "vfat:32"; + else if (type == FAT16) + return "vfat:16"; + else if (type == FAT12) + return "vfat:12"; + + return "unknown"; +} + +static inline struct sdfat_sb_info *SDFAT_SB(struct super_block *sb) +{ + return (struct sdfat_sb_info *)sb->s_fs_info; +} + +static inline struct sdfat_inode_info *SDFAT_I(struct inode *inode) +{ + return container_of(inode, struct sdfat_inode_info, vfs_inode); +} + +/* + * If ->i_mode can't hold S_IWUGO (i.e. ATTR_RO), we use ->i_attrs to + * save ATTR_RO instead of ->i_mode. + * + * If it's directory and !sbi->options.rodir, ATTR_RO isn't read-only + * bit, it's just used as flag for app. + */ +static inline int sdfat_mode_can_hold_ro(struct inode *inode) +{ + struct sdfat_sb_info *sbi = SDFAT_SB(inode->i_sb); + + if (S_ISDIR(inode->i_mode)) + return 0; + + if ((~sbi->options.fs_fmask) & S_IWUGO) + return 1; + return 0; +} + +/* + * FIXME : needs to check symlink option. + */ +/* Convert attribute bits and a mask to the UNIX mode. */ +static inline mode_t sdfat_make_mode(struct sdfat_sb_info *sbi, + u32 attr, mode_t mode) +{ + if ((attr & ATTR_READONLY) && !(attr & ATTR_SUBDIR)) + mode &= ~S_IWUGO; + + if (attr & ATTR_SUBDIR) + return (mode & ~sbi->options.fs_dmask) | S_IFDIR; + else if (attr & ATTR_SYMLINK) + return (mode & ~sbi->options.fs_dmask) | S_IFLNK; + else + return (mode & ~sbi->options.fs_fmask) | S_IFREG; +} + +/* Return the FAT attribute byte for this inode */ +static inline u32 sdfat_make_attr(struct inode *inode) +{ + u32 attrs = SDFAT_I(inode)->fid.attr; + + if (S_ISDIR(inode->i_mode)) + attrs |= ATTR_SUBDIR; + if (sdfat_mode_can_hold_ro(inode) && !(inode->i_mode & S_IWUGO)) + attrs |= ATTR_READONLY; + return attrs; +} + +static inline void sdfat_save_attr(struct inode *inode, u32 attr) +{ + if (sdfat_mode_can_hold_ro(inode)) + SDFAT_I(inode)->fid.attr = attr & ATTR_RWMASK; + else + SDFAT_I(inode)->fid.attr = attr & (ATTR_RWMASK | ATTR_READONLY); +} + +/* sdfat/statistics.c */ +/* bigdata function */ +#ifdef CONFIG_SDFAT_STATISTICS +extern int sdfat_statistics_init(struct kset *sdfat_kset); +extern void sdfat_statistics_uninit(void); +extern void sdfat_statistics_set_mnt(FS_INFO_T *fsi); +extern void sdfat_statistics_set_mkdir(u8 flags); +extern void sdfat_statistics_set_create(u8 flags); +extern void sdfat_statistics_set_rw(u8 flags, u32 clu_offset, s32 create); +extern void sdfat_statistics_set_trunc(u8 flags, CHAIN_T *clu); +extern void sdfat_statistics_set_vol_size(struct super_block *sb); +#else +static inline int sdfat_statistics_init(struct kset *sdfat_kset) +{ + return 0; +} +static inline void sdfat_statistics_uninit(void) {}; +static inline void sdfat_statistics_set_mnt(FS_INFO_T *fsi) {}; +static inline void sdfat_statistics_set_mkdir(u8 flags) {}; +static inline void sdfat_statistics_set_create(u8 flags) {}; +static inline void sdfat_statistics_set_rw(u8 flags, u32 clu_offset, s32 create) {}; +static inline void sdfat_statistics_set_trunc(u8 flags, CHAIN_T *clu) {}; +static inline void sdfat_statistics_set_vol_size(struct super_block *sb) {}; +#endif + +/* sdfat/nls.c */ +/* NLS management function */ +s32 nls_cmp_sfn(struct super_block *sb, u8 *a, u8 *b); +s32 nls_cmp_uniname(struct super_block *sb, u16 *a, u16 *b); +s32 nls_uni16s_to_sfn(struct super_block *sb, UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname, s32 *p_lossy); +s32 nls_sfn_to_uni16s(struct super_block *sb, DOS_NAME_T *p_dosname, UNI_NAME_T *p_uniname); +s32 nls_uni16s_to_vfsname(struct super_block *sb, UNI_NAME_T *uniname, u8 *p_cstring, s32 len); +s32 nls_vfsname_to_uni16s(struct super_block *sb, const u8 *p_cstring, + const s32 len, UNI_NAME_T *uniname, s32 *p_lossy); + +/* sdfat/mpage.c */ +#ifdef CONFIG_SDFAT_ALIGNED_MPAGE_WRITE +int sdfat_mpage_writepages(struct address_space *mapping, + struct writeback_control *wbc, get_block_t *get_block); +#endif + +/* sdfat/xattr.c */ +#ifdef CONFIG_SDFAT_VIRTUAL_XATTR +void setup_sdfat_xattr_handler(struct super_block *sb); +extern int sdfat_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); +extern ssize_t sdfat_getxattr(struct dentry *dentry, const char *name, void *value, size_t size); +extern ssize_t sdfat_listxattr(struct dentry *dentry, char *list, size_t size); +extern int sdfat_removexattr(struct dentry *dentry, const char *name); +#else +static inline void setup_sdfat_xattr_handler(struct super_block *sb) {}; +#endif + +/* sdfat/misc.c */ +extern void +__sdfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) + __printf(3, 4) __cold; +#define sdfat_fs_error(sb, fmt, args...) \ + __sdfat_fs_error(sb, 1, fmt, ## args) +#define sdfat_fs_error_ratelimit(sb, fmt, args...) \ + __sdfat_fs_error(sb, __ratelimit(&SDFAT_SB(sb)->ratelimit), fmt, ## args) +extern void +__sdfat_msg(struct super_block *sb, const char *lv, int st, const char *fmt, ...) + __printf(4, 5) __cold; +#define sdfat_msg(sb, lv, fmt, args...) \ + __sdfat_msg(sb, lv, 0, fmt, ## args) +#define sdfat_log_msg(sb, lv, fmt, args...) \ + __sdfat_msg(sb, lv, 1, fmt, ## args) +extern void sdfat_log_version(void); +extern void sdfat_time_fat2unix(struct sdfat_sb_info *sbi, struct timespec *ts, + DATE_TIME_T *tp); +extern void sdfat_time_unix2fat(struct sdfat_sb_info *sbi, struct timespec *ts, + DATE_TIME_T *tp); +extern TIMESTAMP_T *tm_now(struct sdfat_sb_info *sbi, TIMESTAMP_T *tm); + +#ifdef CONFIG_SDFAT_DEBUG + +#ifdef CONFIG_SDFAT_DBG_CAREFUL +void sdfat_debug_check_clusters(struct inode *inode); +#else +#define sdfat_debug_check_clusters(inode) +#endif /* CONFIG_SDFAT_DBG_CAREFUL */ + +#ifdef CONFIG_SDFAT_DBG_BUGON +#define sdfat_debug_bug_on(expr) BUG_ON(expr) +#else +#define sdfat_debug_bug_on(expr) +#endif + +#ifdef CONFIG_SDFAT_DBG_WARNON +#define sdfat_debug_warn_on(expr) WARN_ON(expr) +#else +#define sdfat_debug_warn_on(expr) +#endif + +#else /* CONFIG_SDFAT_DEBUG */ + +#define sdfat_debug_check_clusters(inode) +#define sdfat_debug_bug_on(expr) +#define sdfat_debug_warn_on(expr) + +#endif /* CONFIG_SDFAT_DEBUG */ + +#ifdef CONFIG_SDFAT_TRACE_ELAPSED_TIME +u32 sdfat_time_current_usec(struct timeval *tv); +extern struct timeval __t1; +extern struct timeval __t2; + +#define TIME_GET(tv) sdfat_time_current_usec(tv) +#define TIME_START(s) sdfat_time_current_usec(s) +#define TIME_END(e) sdfat_time_current_usec(e) +#define TIME_ELAPSED(s, e) ((u32)(((e)->tv_sec - (s)->tv_sec) * 1000000 + \ + ((e)->tv_usec - (s)->tv_usec))) +#define PRINT_TIME(n) pr_info("[SDFAT] Elapsed time %d = %d (usec)\n", n, (__t2 - __t1)) +#else /* CONFIG_SDFAT_TRACE_ELAPSED_TIME */ +#define TIME_GET(tv) (0) +#define TIME_START(s) +#define TIME_END(e) +#define TIME_ELAPSED(s, e) (0) +#define PRINT_TIME(n) +#endif /* CONFIG_SDFAT_TRACE_ELAPSED_TIME */ + +#define SDFAT_MSG_LV_NONE (0x00000000) +#define SDFAT_MSG_LV_ERR (0x00000001) +#define SDFAT_MSG_LV_INFO (0x00000002) +#define SDFAT_MSG_LV_DBG (0x00000003) +#define SDFAT_MSG_LV_MORE (0x00000004) +#define SDFAT_MSG_LV_TRACE (0x00000005) +#define SDFAT_MSG_LV_ALL (0x00000006) + +#define SDFAT_MSG_LEVEL SDFAT_MSG_LV_INFO + +#define SDFAT_TAG_NAME "SDFAT" +#define __S(x) #x +#define _S(x) __S(x) + +extern void __sdfat_dmsg(int level, const char *fmt, ...) __printf(2, 3) __cold; + +#define SDFAT_EMSG_T(level, ...) \ + __sdfat_dmsg(level, KERN_ERR "[" SDFAT_TAG_NAME "] [" _S(__FILE__) "(" _S(__LINE__) ")] " __VA_ARGS__) +#define SDFAT_DMSG_T(level, ...) \ + __sdfat_dmsg(level, KERN_INFO "[" SDFAT_TAG_NAME "] " __VA_ARGS__) + +#define SDFAT_EMSG(...) SDFAT_EMSG_T(SDFAT_MSG_LV_ERR, __VA_ARGS__) +#define SDFAT_IMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_INFO, __VA_ARGS__) +#define SDFAT_DMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_DBG, __VA_ARGS__) +#define SDFAT_MMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_MORE, __VA_ARGS__) +#define SDFAT_TMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_TRACE, __VA_ARGS__) + +#define EMSG(...) +#define IMSG(...) +#define DMSG(...) +#define MMSG(...) +#define TMSG(...) + +#define EMSG_VAR(exp) +#define IMSG_VAR(exp) +#define DMSG_VAR(exp) +#define MMSG_VAR(exp) +#define TMSG_VAR(exp) + +#ifdef CONFIG_SDFAT_DBG_MSG + + +#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_ERR) +#undef EMSG +#undef EMSG_VAR +#define EMSG(...) SDFAT_EMSG(__VA_ARGS__) +#define EMSG_VAR(exp) exp +#endif + +#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_INFO) +#undef IMSG +#undef IMSG_VAR +#define IMSG(...) SDFAT_IMSG(__VA_ARGS__) +#define IMSG_VAR(exp) exp +#endif + +#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_DBG) +#undef DMSG +#undef DMSG_VAR +#define DMSG(...) SDFAT_DMSG(__VA_ARGS__) +#define DMSG_VAR(exp) exp +#endif + +#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_MORE) +#undef MMSG +#undef MMSG_VAR +#define MMSG(...) SDFAT_MMSG(__VA_ARGS__) +#define MMSG_VAR(exp) exp +#endif + +/* should replace with trace function */ +#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_TRACE) +#undef TMSG +#undef TMSG_VAR +#define TMSG(...) SDFAT_TMSG(__VA_ARGS__) +#define TMSG_VAR(exp) exp +#endif + +#endif /* CONFIG_SDFAT_DBG_MSG */ + + +#define ASSERT(expr) { \ + if (!(expr)) { \ + pr_err("Assertion failed! %s\n", #expr); \ + BUG_ON(1); \ + } \ +} + +#endif /* !_SDFAT_H */ + diff --git a/fs/sdfat/sdfat_fs.h b/fs/sdfat/sdfat_fs.h new file mode 100644 index 0000000000000..998ca84c9a6ee --- /dev/null +++ b/fs/sdfat/sdfat_fs.h @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _SDFAT_FS_H +#define _SDFAT_FS_H + +#include +#include +#include + +/*----------------------------------------------------------------------*/ +/* Constant & Macro Definitions */ +/*----------------------------------------------------------------------*/ +#ifndef MSDOS_SUPER_MAGIC +#define MSDOS_SUPER_MAGIC 0x4d44 /* MD */ +#endif + +#ifndef EXFAT_SUPER_MAGIC +#define EXFAT_SUPER_MAGIC (0x2011BAB0UL) +#endif /* EXFAT_SUPER_MAGIC */ + +#define SDFAT_SUPER_MAGIC (0x5EC5DFA4UL) +#define SDFAT_ROOT_INO 1 + +/* FAT types */ +#define FAT12 0x01 // FAT12 +#define FAT16 0x0E // Win95 FAT16 (LBA) +#define FAT32 0x0C // Win95 FAT32 (LBA) +#define EXFAT 0x07 // exFAT + +/* directory file name */ +#define DOS_CUR_DIR_NAME ". " +#define DOS_PAR_DIR_NAME ".. " + +#ifdef __LITTLE_ENDIAN +#define UNI_CUR_DIR_NAME ".\0" +#define UNI_PAR_DIR_NAME ".\0.\0" +#else +#define UNI_CUR_DIR_NAME "\0." +#define UNI_PAR_DIR_NAME "\0.\0." +#endif + +/* file name lengths */ +/* NOTE : + * The maximum length of input or output is limited to 256 including NULL, + * But we allocate 4 extra bytes for utf8 translation reside in last position, + * because utf8 can uses memory upto 6 bytes per one character. + * Therefore, MAX_CHARSET_SIZE supports upto 6 bytes for utf8 + */ +#define MAX_UNINAME_BUF_SIZE (((MAX_NAME_LENGTH+1)*2)+4) +#define MAX_DOSNAME_BUF_SIZE ((DOS_NAME_LENGTH+2)+6) +#define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH+1)*MAX_CHARSET_SIZE) +#define MAX_CHARSET_SIZE 6 // max size of multi-byte character +#define MAX_NAME_LENGTH 255 // max len of file name excluding NULL +#define DOS_NAME_LENGTH 11 // DOS file name length excluding NULL + +#define DENTRY_SIZE 32 /* directory entry size */ +#define DENTRY_SIZE_BITS 5 + +#define MAX_FAT_DENTRIES 65536 /* FAT allows 65536 directory entries */ +#define MAX_EXFAT_DENTRIES 8388608 /* exFAT allows 8388608(256MB) directory entries */ + +/* PBR entries */ +#define PBR_SIGNATURE 0xAA55 +#define EXT_SIGNATURE 0xAA550000 +#define VOL_LABEL "NO NAME " /* size should be 11 */ +#define OEM_NAME "MSWIN4.1" /* size should be 8 */ +#define STR_FAT12 "FAT12 " /* size should be 8 */ +#define STR_FAT16 "FAT16 " /* size should be 8 */ +#define STR_FAT32 "FAT32 " /* size should be 8 */ +#define STR_EXFAT "EXFAT " /* size should be 8 */ + +#define VOL_CLEAN 0x0000 +#define VOL_DIRTY 0x0002 + +#define FAT_VOL_DIRTY 0x01 + +/* max number of clusters */ +#define FAT12_THRESHOLD 4087 // 2^12 - 1 + 2 (clu 0 & 1) +#define FAT16_THRESHOLD 65527 // 2^16 - 1 + 2 +#define FAT32_THRESHOLD 268435457 // 2^28 - 1 + 2 +#define EXFAT_THRESHOLD 268435457 // 2^28 - 1 + 2 + +/* dentry types */ +#define MSDOS_DELETED 0xE5 /* deleted mark */ +#define MSDOS_UNUSED 0x00 /* end of directory */ + +#define EXFAT_UNUSED 0x00 /* end of directory */ +#define IS_EXFAT_DELETED(x) ((x) < 0x80) /* deleted file (0x01~0x7F) */ +#define EXFAT_INVAL 0x80 /* invalid value */ +#define EXFAT_BITMAP 0x81 /* allocation bitmap */ +#define EXFAT_UPCASE 0x82 /* upcase table */ +#define EXFAT_VOLUME 0x83 /* volume label */ +#define EXFAT_FILE 0x85 /* file or dir */ +#define EXFAT_STREAM 0xC0 /* stream entry */ +#define EXFAT_NAME 0xC1 /* file name entry */ +#define EXFAT_ACL 0xC2 /* stream entry */ + +/* specific flag */ +#define MSDOS_LAST_LFN 0x40 + +/* file attributes */ +#define ATTR_NORMAL 0x0000 +#define ATTR_READONLY 0x0001 +#define ATTR_HIDDEN 0x0002 +#define ATTR_SYSTEM 0x0004 +#define ATTR_VOLUME 0x0008 +#define ATTR_SUBDIR 0x0010 +#define ATTR_ARCHIVE 0x0020 +#define ATTR_SYMLINK 0x0040 +#define ATTR_EXTEND (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | \ + ATTR_VOLUME) /* 0x000F */ + +#define ATTR_EXTEND_MASK (ATTR_EXTEND | ATTR_SUBDIR | ATTR_ARCHIVE) +#define ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ + ATTR_SUBDIR | ATTR_ARCHIVE | ATTR_SYMLINK)/* 0x007E */ + +/* file creation modes */ +#define FM_REGULAR 0x00 +#define FM_SYMLINK 0x40 + +/* time modes */ +#define TM_CREATE 0 +#define TM_MODIFY 1 +#define TM_ACCESS 2 + +/* checksum types */ +#define CS_DIR_ENTRY 0 +#define CS_PBR_SECTOR 1 +#define CS_DEFAULT 2 + +/* + * ioctl command + */ +#define SDFAT_IOCTL_GET_VOLUME_ID _IOR('r', 0x12, __u32) +#define SDFAT_IOCTL_DFR_INFO _IOC(_IOC_NONE, 'E', 0x13, sizeof(u32)) +#define SDFAT_IOCTL_DFR_TRAV _IOC(_IOC_NONE, 'E', 0x14, sizeof(u32)) +#define SDFAT_IOCTL_DFR_REQ _IOC(_IOC_NONE, 'E', 0x15, sizeof(u32)) +#define SDFAT_IOCTL_DFR_SPO_FLAG _IOC(_IOC_NONE, 'E', 0x16, sizeof(u32)) +#define SDFAT_IOCTL_PANIC _IOC(_IOC_NONE, 'E', 0x17, sizeof(u32)) + +/* + * ioctl command for debugging + */ + +/* + * IOCTL code 'f' used by + * - file systems typically #0~0x1F + * - embedded terminal devices #128~ + * - exts for debugging purpose #99 + * number 100 and 101 is available now but has possible conflicts + * + * NOTE : This is available only If CONFIG_SDFAT_DVBG_IOCTL is enabled. + * + */ +#define SDFAT_IOC_GET_DEBUGFLAGS _IOR('f', 100, long) +#define SDFAT_IOC_SET_DEBUGFLAGS _IOW('f', 101, long) + +#define SDFAT_DEBUGFLAGS_INVALID_UMOUNT 0x01 +#define SDFAT_DEBUGFLAGS_ERROR_RW 0x02 + +/*----------------------------------------------------------------------*/ +/* On-Disk Type Definitions */ +/*----------------------------------------------------------------------*/ + +/* FAT12/16 BIOS parameter block (64 bytes) */ +typedef struct { + __u8 jmp_boot[3]; + __u8 oem_name[8]; + + __u8 sect_size[2]; /* unaligned */ + __u8 sect_per_clus; + __le16 num_reserved; /* . */ + __u8 num_fats; + __u8 num_root_entries[2]; /* unaligned */ + __u8 num_sectors[2]; /* unaligned */ + __u8 media_type; + __le16 num_fat_sectors; + __le16 sectors_in_track; + __le16 num_heads; + __le32 num_hid_sectors; /* . */ + __le32 num_huge_sectors; + + __u8 phy_drv_no; + __u8 state; /* used by WindowsNT for mount state */ + __u8 ext_signature; + __u8 vol_serial[4]; + __u8 vol_label[11]; + __u8 vol_type[8]; + __le16 dummy; +} bpb16_t; + +/* FAT32 BIOS parameter block (64 bytes) */ +typedef struct { + __u8 jmp_boot[3]; + __u8 oem_name[8]; + + __u8 sect_size[2]; /* unaligned */ + __u8 sect_per_clus; + __le16 num_reserved; + __u8 num_fats; + __u8 num_root_entries[2]; /* unaligned */ + __u8 num_sectors[2]; /* unaligned */ + __u8 media_type; + __le16 num_fat_sectors; /* zero */ + __le16 sectors_in_track; + __le16 num_heads; + __le32 num_hid_sectors; /* . */ + __le32 num_huge_sectors; + + __le32 num_fat32_sectors; + __le16 ext_flags; + __u8 fs_version[2]; + __le32 root_cluster; /* . */ + __le16 fsinfo_sector; + __le16 backup_sector; + __le16 reserved[6]; /* . */ +} bpb32_t; + +/* FAT32 EXTEND BIOS parameter block (32 bytes) */ +typedef struct { + __u8 phy_drv_no; + __u8 state; /* used by WindowsNT for mount state */ + __u8 ext_signature; + __u8 vol_serial[4]; + __u8 vol_label[11]; + __u8 vol_type[8]; + __le16 dummy[3]; +} bsx32_t; + +/* EXFAT BIOS parameter block (64 bytes) */ +typedef struct { + __u8 jmp_boot[3]; + __u8 oem_name[8]; + __u8 res_zero[53]; +} bpb64_t; + +/* EXFAT EXTEND BIOS parameter block (56 bytes) */ +typedef struct { + __le64 vol_offset; + __le64 vol_length; + __le32 fat_offset; + __le32 fat_length; + __le32 clu_offset; + __le32 clu_count; + __le32 root_cluster; + __le32 vol_serial; + __u8 fs_version[2]; + __le16 vol_flags; + __u8 sect_size_bits; + __u8 sect_per_clus_bits; + __u8 num_fats; + __u8 phy_drv_no; + __u8 perc_in_use; + __u8 reserved2[7]; +} bsx64_t; + +/* FAT32 PBR (64 bytes) */ +typedef struct { + bpb16_t bpb; +} pbr16_t; + +/* FAT32 PBR[BPB+BSX] (96 bytes) */ +typedef struct { + bpb32_t bpb; + bsx32_t bsx; +} pbr32_t; + +/* EXFAT PBR[BPB+BSX] (120 bytes) */ +typedef struct { + bpb64_t bpb; + bsx64_t bsx; +} pbr64_t; + +/* Common PBR[Partition Boot Record] (512 bytes) */ +typedef struct { + union { + __u8 raw[64]; + bpb16_t f16; + bpb32_t f32; + bpb64_t f64; + } bpb; + union { + __u8 raw[56]; + bsx32_t f32; + bsx64_t f64; + } bsx; + __u8 boot_code[390]; + __le16 signature; +} pbr_t; + +/* FAT32 filesystem information sector (512 bytes) */ +typedef struct { + __le32 signature1; // aligned + __u8 reserved1[480]; + __le32 signature2; // aligned + __le32 free_cluster; // aligned + __le32 next_cluster; // aligned + __u8 reserved2[14]; + __le16 signature3[2]; +} fat32_fsi_t; + +/* FAT directory entry (32 bytes) */ +typedef struct { + __u8 dummy[32]; +} DENTRY_T; + +typedef struct { + __u8 name[DOS_NAME_LENGTH]; /* 11 chars */ + __u8 attr; + __u8 lcase; + __u8 create_time_ms; + __le16 create_time; // aligned + __le16 create_date; // aligned + __le16 access_date; // aligned + __le16 start_clu_hi; // aligned + __le16 modify_time; // aligned + __le16 modify_date; // aligned + __le16 start_clu_lo; // aligned + __le32 size; // aligned +} DOS_DENTRY_T; + +/* FAT extended directory entry (32 bytes) */ +typedef struct { + __u8 order; + __u8 unicode_0_4[10]; + __u8 attr; + __u8 sysid; + __u8 checksum; + __le16 unicode_5_10[6]; // aligned + __le16 start_clu; // aligned + __le16 unicode_11_12[2]; // aligned +} EXT_DENTRY_T; + +/* EXFAT file directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 num_ext; + __le16 checksum; // aligned + __le16 attr; // aligned + __le16 reserved1; + __le16 create_time; // aligned + __le16 create_date; // aligned + __le16 modify_time; // aligned + __le16 modify_date; // aligned + __le16 access_time; // aligned + __le16 access_date; // aligned + __u8 create_time_ms; + __u8 modify_time_ms; + __u8 access_time_ms; + __u8 reserved2[9]; +} FILE_DENTRY_T; + +/* EXFAT stream extension directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 flags; + __u8 reserved1; + __u8 name_len; + __le16 name_hash; // aligned + __le16 reserved2; + __le64 valid_size; // aligned + __le32 reserved3; // aligned + __le32 start_clu; // aligned + __le64 size; // aligned +} STRM_DENTRY_T; + +/* EXFAT file name directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 flags; + __le16 unicode_0_14[15]; // aligned +} NAME_DENTRY_T; + +/* EXFAT allocation bitmap directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 flags; + __u8 reserved[18]; + __le32 start_clu; // aligned + __le64 size; // aligned +} BMAP_DENTRY_T; + +/* EXFAT up-case table directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 reserved1[3]; + __le32 checksum; // aligned + __u8 reserved2[12]; + __le32 start_clu; // aligned + __le64 size; // aligned +} CASE_DENTRY_T; + +/* EXFAT volume label directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 label_len; + __le16 unicode_0_10[11]; // aligned + __u8 reserved[8]; +} VOLM_DENTRY_T; + +#endif /* _SDFAT_FS_H */ diff --git a/fs/sdfat/statistics.c b/fs/sdfat/statistics.c new file mode 100644 index 0000000000000..00624df5a6364 --- /dev/null +++ b/fs/sdfat/statistics.c @@ -0,0 +1,262 @@ +#include "sdfat.h" + +#define SDFAT_VF_CLUS_MAX 7 /* 512 Byte ~ 32 KByte */ +#define SDFAT_EF_CLUS_MAX 17 /* 512 Byte ~ 32 MByte */ + +enum { + SDFAT_MNT_FAT12, + SDFAT_MNT_FAT16, + SDFAT_MNT_FAT32, + SDFAT_MNT_EXFAT, + SDFAT_MNT_MAX +}; + +enum { + SDFAT_OP_EXFAT_MNT, + SDFAT_OP_MKDIR, + SDFAT_OP_CREATE, + SDFAT_OP_READ, + SDFAT_OP_WRITE, + SDFAT_OP_TRUNC, + SDFAT_OP_MAX +}; + +enum { + SDFAT_VOL_4G, + SDFAT_VOL_8G, + SDFAT_VOL_16G, + SDFAT_VOL_32G, + SDFAT_VOL_64G, + SDFAT_VOL_128G, + SDFAT_VOL_256G, + SDFAT_VOL_512G, + SDFAT_VOL_XTB, + SDFAT_VOL_MAX +}; + +static struct sdfat_statistics { + u32 clus_vfat[SDFAT_VF_CLUS_MAX]; + u32 clus_exfat[SDFAT_EF_CLUS_MAX]; + u32 mnt_cnt[SDFAT_MNT_MAX]; + u32 nofat_op[SDFAT_OP_MAX]; + u32 vol_size[SDFAT_VOL_MAX]; +} statistics; + +static struct kset *sdfat_statistics_kset; + +static ssize_t vfat_cl_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "VCL_512B_I:%u,VCL_1K_I:%u,VCL_2K_I:%u," + "VCL_4K_I:%u,VCL_8K_I:%u,VCL_16K_I:%u,VCL_32K_I:%u\n", + statistics.clus_vfat[0], statistics.clus_vfat[1], + statistics.clus_vfat[2], statistics.clus_vfat[3], + statistics.clus_vfat[4], statistics.clus_vfat[5], + statistics.clus_vfat[6]); +} + +static ssize_t exfat_cl_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "ECL_512B_I:%u,ECL_1K_I:%u,ECL_2K_I:%u," + "ECL_4K_I:%u,ECL_8K_I:%u,ECL_16K_I:%u,ECL_32K_I:%u,ECL_64K_I:%u," + "ECL_128K_I:%u,ECL_256K_I:%u,ECL_512K_I:%u,ECL_1M_I:%u," + "ECL_2M_I:%u,ECL_4M_I:%u,ECL_8M_I:%u,ECL_16M_I:%u,ECL_32M_I:%u\n", + statistics.clus_exfat[0], statistics.clus_exfat[1], + statistics.clus_exfat[2], statistics.clus_exfat[3], + statistics.clus_exfat[4], statistics.clus_exfat[5], + statistics.clus_exfat[6], statistics.clus_exfat[7], + statistics.clus_exfat[8], statistics.clus_exfat[9], + statistics.clus_exfat[10], statistics.clus_exfat[11], + statistics.clus_exfat[12], statistics.clus_exfat[13], + statistics.clus_exfat[14], statistics.clus_exfat[15], + statistics.clus_exfat[16]); +} + +static ssize_t mount_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "FAT12_MNT_I:%u,FAT16_MNT_I:%u,FAT32_MNT_I:%u," + "EXFAT_MNT_I:%u\n", + statistics.mnt_cnt[SDFAT_MNT_FAT12], + statistics.mnt_cnt[SDFAT_MNT_FAT16], + statistics.mnt_cnt[SDFAT_MNT_FAT32], + statistics.mnt_cnt[SDFAT_MNT_EXFAT]); +} + +static ssize_t nofat_op_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "NOFAT_MOUNT_I:%u,NOFAT_MKDIR_I:%u,NOFAT_CREATE_I:%u," + "NOFAT_READ_I:%u,NOFAT_WRITE_I:%u,NOFAT_TRUNC_I:%u\n", + statistics.nofat_op[SDFAT_OP_EXFAT_MNT], + statistics.nofat_op[SDFAT_OP_MKDIR], + statistics.nofat_op[SDFAT_OP_CREATE], + statistics.nofat_op[SDFAT_OP_READ], + statistics.nofat_op[SDFAT_OP_WRITE], + statistics.nofat_op[SDFAT_OP_TRUNC]); +} + +static ssize_t vol_size_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "VOL_4G_I:%u,VOL_8G_I:%u,VOL_16G_I:%u," + "VOL_32G_I:%u,VOL_64G_I:%u,VOL_128G_I:%u,VOL_256G_I:%u," + "VOL_512G_I:%u,VOL_XTB_I:%u\n", + statistics.vol_size[SDFAT_VOL_4G], + statistics.vol_size[SDFAT_VOL_8G], + statistics.vol_size[SDFAT_VOL_16G], + statistics.vol_size[SDFAT_VOL_32G], + statistics.vol_size[SDFAT_VOL_64G], + statistics.vol_size[SDFAT_VOL_128G], + statistics.vol_size[SDFAT_VOL_256G], + statistics.vol_size[SDFAT_VOL_512G], + statistics.vol_size[SDFAT_VOL_XTB]); +} + +static struct kobj_attribute vfat_cl_attr = __ATTR_RO(vfat_cl); +static struct kobj_attribute exfat_cl_attr = __ATTR_RO(exfat_cl); +static struct kobj_attribute mount_attr = __ATTR_RO(mount); +static struct kobj_attribute nofat_op_attr = __ATTR_RO(nofat_op); +static struct kobj_attribute vol_size_attr = __ATTR_RO(vol_size); + +static struct attribute *attributes_statistics[] = { + &vfat_cl_attr.attr, + &exfat_cl_attr.attr, + &mount_attr.attr, + &nofat_op_attr.attr, + &vol_size_attr.attr, + NULL, +}; + +static struct attribute_group attr_group_statistics = { + .attrs = attributes_statistics, +}; + +int sdfat_statistics_init(struct kset *sdfat_kset) +{ + int err; + + sdfat_statistics_kset = kset_create_and_add("statistics", NULL, &sdfat_kset->kobj); + if (!sdfat_statistics_kset) { + pr_err("[SDFAT] failed to create sdfat statistics kobj\n"); + return -ENOMEM; + } + + err = sysfs_create_group(&sdfat_statistics_kset->kobj, &attr_group_statistics); + if (err) { + pr_err("[SDFAT] failed to create sdfat statistics attributes\n"); + kset_unregister(sdfat_statistics_kset); + sdfat_statistics_kset = NULL; + return err; + } + + return 0; +} + +void sdfat_statistics_uninit(void) +{ + if (sdfat_statistics_kset) { + sysfs_remove_group(&sdfat_statistics_kset->kobj, &attr_group_statistics); + kset_unregister(sdfat_statistics_kset); + sdfat_statistics_kset = NULL; + } + memset(&statistics, 0, sizeof(struct sdfat_statistics)); +} + +void sdfat_statistics_set_mnt(FS_INFO_T *fsi) +{ + if (fsi->vol_type == EXFAT) { + statistics.mnt_cnt[SDFAT_MNT_EXFAT]++; + statistics.nofat_op[SDFAT_OP_EXFAT_MNT] = 1; + if (fsi->sect_per_clus_bits < SDFAT_EF_CLUS_MAX) + statistics.clus_exfat[fsi->sect_per_clus_bits]++; + else + statistics.clus_exfat[SDFAT_EF_CLUS_MAX - 1]++; + return; + } + + if (fsi->vol_type == FAT32) + statistics.mnt_cnt[SDFAT_MNT_FAT32]++; + else if (fsi->vol_type == FAT16) + statistics.mnt_cnt[SDFAT_MNT_FAT16]++; + else if (fsi->vol_type == FAT12) + statistics.mnt_cnt[SDFAT_MNT_FAT12]++; + + if (fsi->sect_per_clus_bits < SDFAT_VF_CLUS_MAX) + statistics.clus_vfat[fsi->sect_per_clus_bits]++; + else + statistics.clus_vfat[SDFAT_VF_CLUS_MAX - 1]++; +} + +void sdfat_statistics_set_mkdir(u8 flags) +{ + if (flags != 0x03) + return; + statistics.nofat_op[SDFAT_OP_MKDIR] = 1; +} + +void sdfat_statistics_set_create(u8 flags) +{ + if (flags != 0x03) + return; + statistics.nofat_op[SDFAT_OP_CREATE] = 1; +} + +/* flags : file or dir flgas, 0x03 means no fat-chain. + * clu_offset : file or dir logical cluster offset + * create : BMAP_ADD_CLUSTER or not + * + * File or dir have BMAP_ADD_CLUSTER is no fat-chain write + * when they have 0x03 flag and two or more clusters. + * And don`t have BMAP_ADD_CLUSTER is no fat-chain read + * when above same condition. + */ +void sdfat_statistics_set_rw(u8 flags, u32 clu_offset, s32 create) +{ + if ((flags == 0x03) && (clu_offset > 1)) { + if (create) + statistics.nofat_op[SDFAT_OP_WRITE] = 1; + else + statistics.nofat_op[SDFAT_OP_READ] = 1; + } +} + +/* flags : file or dir flgas, 0x03 means no fat-chain. + * clu : cluster chain + * + * Set no fat-chain trunc when file or dir have 0x03 flag + * and two or more clusters. + */ +void sdfat_statistics_set_trunc(u8 flags, CHAIN_T *clu) +{ + if ((flags == 0x03) && (clu->size > 1)) + statistics.nofat_op[SDFAT_OP_TRUNC] = 1; +} + +void sdfat_statistics_set_vol_size(struct super_block *sb) +{ + u64 vol_size; + FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); + + vol_size = (u64)fsi->num_sectors << sb->s_blocksize_bits; + + if (vol_size <= ((u64)1 << 32)) + statistics.vol_size[SDFAT_VOL_4G]++; + else if (vol_size <= ((u64)1 << 33)) + statistics.vol_size[SDFAT_VOL_8G]++; + else if (vol_size <= ((u64)1 << 34)) + statistics.vol_size[SDFAT_VOL_16G]++; + else if (vol_size <= ((u64)1 << 35)) + statistics.vol_size[SDFAT_VOL_32G]++; + else if (vol_size <= ((u64)1 << 36)) + statistics.vol_size[SDFAT_VOL_64G]++; + else if (vol_size <= ((u64)1 << 37)) + statistics.vol_size[SDFAT_VOL_128G]++; + else if (vol_size <= ((u64)1 << 38)) + statistics.vol_size[SDFAT_VOL_256G]++; + else if (vol_size <= ((u64)1 << 39)) + statistics.vol_size[SDFAT_VOL_512G]++; + else + statistics.vol_size[SDFAT_VOL_XTB]++; +} diff --git a/fs/sdfat/upcase.h b/fs/sdfat/upcase.h new file mode 100644 index 0000000000000..386772c57f8d9 --- /dev/null +++ b/fs/sdfat/upcase.h @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef _UPCASE_H +#define _UPCASE_H + +/* Upcase tabel macro */ +#define SDFAT_NUM_UPCASE 2918 +#define HIGH_INDEX_BIT (8) +#define HIGH_INDEX_MASK (0xFF00) +#define LOW_INDEX_BIT (16-HIGH_INDEX_BIT) +#define UTBL_ROW_COUNT (1<> LOW_INDEX_BIT; +} +static inline u16 get_row_index(u16 i) +{ + return i & ~HIGH_INDEX_MASK; +} + + +static const u8 uni_def_upcase[SDFAT_NUM_UPCASE<<1] = { + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, + 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x00, + 0x18, 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B, 0x00, 0x1C, 0x00, 0x1D, 0x00, 0x1E, 0x00, 0x1F, 0x00, + 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, 0x23, 0x00, 0x24, 0x00, 0x25, 0x00, 0x26, 0x00, 0x27, 0x00, + 0x28, 0x00, 0x29, 0x00, 0x2A, 0x00, 0x2B, 0x00, 0x2C, 0x00, 0x2D, 0x00, 0x2E, 0x00, 0x2F, 0x00, + 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00, 0x37, 0x00, + 0x38, 0x00, 0x39, 0x00, 0x3A, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3D, 0x00, 0x3E, 0x00, 0x3F, 0x00, + 0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00, + 0x48, 0x00, 0x49, 0x00, 0x4A, 0x00, 0x4B, 0x00, 0x4C, 0x00, 0x4D, 0x00, 0x4E, 0x00, 0x4F, 0x00, + 0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53, 0x00, 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, 0x00, + 0x58, 0x00, 0x59, 0x00, 0x5A, 0x00, 0x5B, 0x00, 0x5C, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x5F, 0x00, + 0x60, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00, + 0x48, 0x00, 0x49, 0x00, 0x4A, 0x00, 0x4B, 0x00, 0x4C, 0x00, 0x4D, 0x00, 0x4E, 0x00, 0x4F, 0x00, + 0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53, 0x00, 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, 0x00, + 0x58, 0x00, 0x59, 0x00, 0x5A, 0x00, 0x7B, 0x00, 0x7C, 0x00, 0x7D, 0x00, 0x7E, 0x00, 0x7F, 0x00, + 0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00, 0x84, 0x00, 0x85, 0x00, 0x86, 0x00, 0x87, 0x00, + 0x88, 0x00, 0x89, 0x00, 0x8A, 0x00, 0x8B, 0x00, 0x8C, 0x00, 0x8D, 0x00, 0x8E, 0x00, 0x8F, 0x00, + 0x90, 0x00, 0x91, 0x00, 0x92, 0x00, 0x93, 0x00, 0x94, 0x00, 0x95, 0x00, 0x96, 0x00, 0x97, 0x00, + 0x98, 0x00, 0x99, 0x00, 0x9A, 0x00, 0x9B, 0x00, 0x9C, 0x00, 0x9D, 0x00, 0x9E, 0x00, 0x9F, 0x00, + 0xA0, 0x00, 0xA1, 0x00, 0xA2, 0x00, 0xA3, 0x00, 0xA4, 0x00, 0xA5, 0x00, 0xA6, 0x00, 0xA7, 0x00, + 0xA8, 0x00, 0xA9, 0x00, 0xAA, 0x00, 0xAB, 0x00, 0xAC, 0x00, 0xAD, 0x00, 0xAE, 0x00, 0xAF, 0x00, + 0xB0, 0x00, 0xB1, 0x00, 0xB2, 0x00, 0xB3, 0x00, 0xB4, 0x00, 0xB5, 0x00, 0xB6, 0x00, 0xB7, 0x00, + 0xB8, 0x00, 0xB9, 0x00, 0xBA, 0x00, 0xBB, 0x00, 0xBC, 0x00, 0xBD, 0x00, 0xBE, 0x00, 0xBF, 0x00, + 0xC0, 0x00, 0xC1, 0x00, 0xC2, 0x00, 0xC3, 0x00, 0xC4, 0x00, 0xC5, 0x00, 0xC6, 0x00, 0xC7, 0x00, + 0xC8, 0x00, 0xC9, 0x00, 0xCA, 0x00, 0xCB, 0x00, 0xCC, 0x00, 0xCD, 0x00, 0xCE, 0x00, 0xCF, 0x00, + 0xD0, 0x00, 0xD1, 0x00, 0xD2, 0x00, 0xD3, 0x00, 0xD4, 0x00, 0xD5, 0x00, 0xD6, 0x00, 0xD7, 0x00, + 0xD8, 0x00, 0xD9, 0x00, 0xDA, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0xDD, 0x00, 0xDE, 0x00, 0xDF, 0x00, + 0xC0, 0x00, 0xC1, 0x00, 0xC2, 0x00, 0xC3, 0x00, 0xC4, 0x00, 0xC5, 0x00, 0xC6, 0x00, 0xC7, 0x00, + 0xC8, 0x00, 0xC9, 0x00, 0xCA, 0x00, 0xCB, 0x00, 0xCC, 0x00, 0xCD, 0x00, 0xCE, 0x00, 0xCF, 0x00, + 0xD0, 0x00, 0xD1, 0x00, 0xD2, 0x00, 0xD3, 0x00, 0xD4, 0x00, 0xD5, 0x00, 0xD6, 0x00, 0xF7, 0x00, + 0xD8, 0x00, 0xD9, 0x00, 0xDA, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0xDD, 0x00, 0xDE, 0x00, 0x78, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x02, 0x01, 0x02, 0x01, 0x04, 0x01, 0x04, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x08, 0x01, 0x08, 0x01, 0x0A, 0x01, 0x0A, 0x01, 0x0C, 0x01, 0x0C, 0x01, 0x0E, 0x01, 0x0E, 0x01, + 0x10, 0x01, 0x10, 0x01, 0x12, 0x01, 0x12, 0x01, 0x14, 0x01, 0x14, 0x01, 0x16, 0x01, 0x16, 0x01, + 0x18, 0x01, 0x18, 0x01, 0x1A, 0x01, 0x1A, 0x01, 0x1C, 0x01, 0x1C, 0x01, 0x1E, 0x01, 0x1E, 0x01, + 0x20, 0x01, 0x20, 0x01, 0x22, 0x01, 0x22, 0x01, 0x24, 0x01, 0x24, 0x01, 0x26, 0x01, 0x26, 0x01, + 0x28, 0x01, 0x28, 0x01, 0x2A, 0x01, 0x2A, 0x01, 0x2C, 0x01, 0x2C, 0x01, 0x2E, 0x01, 0x2E, 0x01, + 0x30, 0x01, 0x31, 0x01, 0x32, 0x01, 0x32, 0x01, 0x34, 0x01, 0x34, 0x01, 0x36, 0x01, 0x36, 0x01, + 0x38, 0x01, 0x39, 0x01, 0x39, 0x01, 0x3B, 0x01, 0x3B, 0x01, 0x3D, 0x01, 0x3D, 0x01, 0x3F, 0x01, + 0x3F, 0x01, 0x41, 0x01, 0x41, 0x01, 0x43, 0x01, 0x43, 0x01, 0x45, 0x01, 0x45, 0x01, 0x47, 0x01, + 0x47, 0x01, 0x49, 0x01, 0x4A, 0x01, 0x4A, 0x01, 0x4C, 0x01, 0x4C, 0x01, 0x4E, 0x01, 0x4E, 0x01, + 0x50, 0x01, 0x50, 0x01, 0x52, 0x01, 0x52, 0x01, 0x54, 0x01, 0x54, 0x01, 0x56, 0x01, 0x56, 0x01, + 0x58, 0x01, 0x58, 0x01, 0x5A, 0x01, 0x5A, 0x01, 0x5C, 0x01, 0x5C, 0x01, 0x5E, 0x01, 0x5E, 0x01, + 0x60, 0x01, 0x60, 0x01, 0x62, 0x01, 0x62, 0x01, 0x64, 0x01, 0x64, 0x01, 0x66, 0x01, 0x66, 0x01, + 0x68, 0x01, 0x68, 0x01, 0x6A, 0x01, 0x6A, 0x01, 0x6C, 0x01, 0x6C, 0x01, 0x6E, 0x01, 0x6E, 0x01, + 0x70, 0x01, 0x70, 0x01, 0x72, 0x01, 0x72, 0x01, 0x74, 0x01, 0x74, 0x01, 0x76, 0x01, 0x76, 0x01, + 0x78, 0x01, 0x79, 0x01, 0x79, 0x01, 0x7B, 0x01, 0x7B, 0x01, 0x7D, 0x01, 0x7D, 0x01, 0x7F, 0x01, + 0x43, 0x02, 0x81, 0x01, 0x82, 0x01, 0x82, 0x01, 0x84, 0x01, 0x84, 0x01, 0x86, 0x01, 0x87, 0x01, + 0x87, 0x01, 0x89, 0x01, 0x8A, 0x01, 0x8B, 0x01, 0x8B, 0x01, 0x8D, 0x01, 0x8E, 0x01, 0x8F, 0x01, + 0x90, 0x01, 0x91, 0x01, 0x91, 0x01, 0x93, 0x01, 0x94, 0x01, 0xF6, 0x01, 0x96, 0x01, 0x97, 0x01, + 0x98, 0x01, 0x98, 0x01, 0x3D, 0x02, 0x9B, 0x01, 0x9C, 0x01, 0x9D, 0x01, 0x20, 0x02, 0x9F, 0x01, + 0xA0, 0x01, 0xA0, 0x01, 0xA2, 0x01, 0xA2, 0x01, 0xA4, 0x01, 0xA4, 0x01, 0xA6, 0x01, 0xA7, 0x01, + 0xA7, 0x01, 0xA9, 0x01, 0xAA, 0x01, 0xAB, 0x01, 0xAC, 0x01, 0xAC, 0x01, 0xAE, 0x01, 0xAF, 0x01, + 0xAF, 0x01, 0xB1, 0x01, 0xB2, 0x01, 0xB3, 0x01, 0xB3, 0x01, 0xB5, 0x01, 0xB5, 0x01, 0xB7, 0x01, + 0xB8, 0x01, 0xB8, 0x01, 0xBA, 0x01, 0xBB, 0x01, 0xBC, 0x01, 0xBC, 0x01, 0xBE, 0x01, 0xF7, 0x01, + 0xC0, 0x01, 0xC1, 0x01, 0xC2, 0x01, 0xC3, 0x01, 0xC4, 0x01, 0xC5, 0x01, 0xC4, 0x01, 0xC7, 0x01, + 0xC8, 0x01, 0xC7, 0x01, 0xCA, 0x01, 0xCB, 0x01, 0xCA, 0x01, 0xCD, 0x01, 0xCD, 0x01, 0xCF, 0x01, + 0xCF, 0x01, 0xD1, 0x01, 0xD1, 0x01, 0xD3, 0x01, 0xD3, 0x01, 0xD5, 0x01, 0xD5, 0x01, 0xD7, 0x01, + 0xD7, 0x01, 0xD9, 0x01, 0xD9, 0x01, 0xDB, 0x01, 0xDB, 0x01, 0x8E, 0x01, 0xDE, 0x01, 0xDE, 0x01, + 0xE0, 0x01, 0xE0, 0x01, 0xE2, 0x01, 0xE2, 0x01, 0xE4, 0x01, 0xE4, 0x01, 0xE6, 0x01, 0xE6, 0x01, + 0xE8, 0x01, 0xE8, 0x01, 0xEA, 0x01, 0xEA, 0x01, 0xEC, 0x01, 0xEC, 0x01, 0xEE, 0x01, 0xEE, 0x01, + 0xF0, 0x01, 0xF1, 0x01, 0xF2, 0x01, 0xF1, 0x01, 0xF4, 0x01, 0xF4, 0x01, 0xF6, 0x01, 0xF7, 0x01, + 0xF8, 0x01, 0xF8, 0x01, 0xFA, 0x01, 0xFA, 0x01, 0xFC, 0x01, 0xFC, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x00, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x04, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x02, + 0x08, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x0A, 0x02, 0x0C, 0x02, 0x0C, 0x02, 0x0E, 0x02, 0x0E, 0x02, + 0x10, 0x02, 0x10, 0x02, 0x12, 0x02, 0x12, 0x02, 0x14, 0x02, 0x14, 0x02, 0x16, 0x02, 0x16, 0x02, + 0x18, 0x02, 0x18, 0x02, 0x1A, 0x02, 0x1A, 0x02, 0x1C, 0x02, 0x1C, 0x02, 0x1E, 0x02, 0x1E, 0x02, + 0x20, 0x02, 0x21, 0x02, 0x22, 0x02, 0x22, 0x02, 0x24, 0x02, 0x24, 0x02, 0x26, 0x02, 0x26, 0x02, + 0x28, 0x02, 0x28, 0x02, 0x2A, 0x02, 0x2A, 0x02, 0x2C, 0x02, 0x2C, 0x02, 0x2E, 0x02, 0x2E, 0x02, + 0x30, 0x02, 0x30, 0x02, 0x32, 0x02, 0x32, 0x02, 0x34, 0x02, 0x35, 0x02, 0x36, 0x02, 0x37, 0x02, + 0x38, 0x02, 0x39, 0x02, 0x65, 0x2C, 0x3B, 0x02, 0x3B, 0x02, 0x3D, 0x02, 0x66, 0x2C, 0x3F, 0x02, + 0x40, 0x02, 0x41, 0x02, 0x41, 0x02, 0x43, 0x02, 0x44, 0x02, 0x45, 0x02, 0x46, 0x02, 0x46, 0x02, + 0x48, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x4A, 0x02, 0x4C, 0x02, 0x4C, 0x02, 0x4E, 0x02, 0x4E, 0x02, + 0x50, 0x02, 0x51, 0x02, 0x52, 0x02, 0x81, 0x01, 0x86, 0x01, 0x55, 0x02, 0x89, 0x01, 0x8A, 0x01, + 0x58, 0x02, 0x8F, 0x01, 0x5A, 0x02, 0x90, 0x01, 0x5C, 0x02, 0x5D, 0x02, 0x5E, 0x02, 0x5F, 0x02, + 0x93, 0x01, 0x61, 0x02, 0x62, 0x02, 0x94, 0x01, 0x64, 0x02, 0x65, 0x02, 0x66, 0x02, 0x67, 0x02, + 0x97, 0x01, 0x96, 0x01, 0x6A, 0x02, 0x62, 0x2C, 0x6C, 0x02, 0x6D, 0x02, 0x6E, 0x02, 0x9C, 0x01, + 0x70, 0x02, 0x71, 0x02, 0x9D, 0x01, 0x73, 0x02, 0x74, 0x02, 0x9F, 0x01, 0x76, 0x02, 0x77, 0x02, + 0x78, 0x02, 0x79, 0x02, 0x7A, 0x02, 0x7B, 0x02, 0x7C, 0x02, 0x64, 0x2C, 0x7E, 0x02, 0x7F, 0x02, + 0xA6, 0x01, 0x81, 0x02, 0x82, 0x02, 0xA9, 0x01, 0x84, 0x02, 0x85, 0x02, 0x86, 0x02, 0x87, 0x02, + 0xAE, 0x01, 0x44, 0x02, 0xB1, 0x01, 0xB2, 0x01, 0x45, 0x02, 0x8D, 0x02, 0x8E, 0x02, 0x8F, 0x02, + 0x90, 0x02, 0x91, 0x02, 0xB7, 0x01, 0x93, 0x02, 0x94, 0x02, 0x95, 0x02, 0x96, 0x02, 0x97, 0x02, + 0x98, 0x02, 0x99, 0x02, 0x9A, 0x02, 0x9B, 0x02, 0x9C, 0x02, 0x9D, 0x02, 0x9E, 0x02, 0x9F, 0x02, + 0xA0, 0x02, 0xA1, 0x02, 0xA2, 0x02, 0xA3, 0x02, 0xA4, 0x02, 0xA5, 0x02, 0xA6, 0x02, 0xA7, 0x02, + 0xA8, 0x02, 0xA9, 0x02, 0xAA, 0x02, 0xAB, 0x02, 0xAC, 0x02, 0xAD, 0x02, 0xAE, 0x02, 0xAF, 0x02, + 0xB0, 0x02, 0xB1, 0x02, 0xB2, 0x02, 0xB3, 0x02, 0xB4, 0x02, 0xB5, 0x02, 0xB6, 0x02, 0xB7, 0x02, + 0xB8, 0x02, 0xB9, 0x02, 0xBA, 0x02, 0xBB, 0x02, 0xBC, 0x02, 0xBD, 0x02, 0xBE, 0x02, 0xBF, 0x02, + 0xC0, 0x02, 0xC1, 0x02, 0xC2, 0x02, 0xC3, 0x02, 0xC4, 0x02, 0xC5, 0x02, 0xC6, 0x02, 0xC7, 0x02, + 0xC8, 0x02, 0xC9, 0x02, 0xCA, 0x02, 0xCB, 0x02, 0xCC, 0x02, 0xCD, 0x02, 0xCE, 0x02, 0xCF, 0x02, + 0xD0, 0x02, 0xD1, 0x02, 0xD2, 0x02, 0xD3, 0x02, 0xD4, 0x02, 0xD5, 0x02, 0xD6, 0x02, 0xD7, 0x02, + 0xD8, 0x02, 0xD9, 0x02, 0xDA, 0x02, 0xDB, 0x02, 0xDC, 0x02, 0xDD, 0x02, 0xDE, 0x02, 0xDF, 0x02, + 0xE0, 0x02, 0xE1, 0x02, 0xE2, 0x02, 0xE3, 0x02, 0xE4, 0x02, 0xE5, 0x02, 0xE6, 0x02, 0xE7, 0x02, + 0xE8, 0x02, 0xE9, 0x02, 0xEA, 0x02, 0xEB, 0x02, 0xEC, 0x02, 0xED, 0x02, 0xEE, 0x02, 0xEF, 0x02, + 0xF0, 0x02, 0xF1, 0x02, 0xF2, 0x02, 0xF3, 0x02, 0xF4, 0x02, 0xF5, 0x02, 0xF6, 0x02, 0xF7, 0x02, + 0xF8, 0x02, 0xF9, 0x02, 0xFA, 0x02, 0xFB, 0x02, 0xFC, 0x02, 0xFD, 0x02, 0xFE, 0x02, 0xFF, 0x02, + 0x00, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x03, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x07, 0x03, + 0x08, 0x03, 0x09, 0x03, 0x0A, 0x03, 0x0B, 0x03, 0x0C, 0x03, 0x0D, 0x03, 0x0E, 0x03, 0x0F, 0x03, + 0x10, 0x03, 0x11, 0x03, 0x12, 0x03, 0x13, 0x03, 0x14, 0x03, 0x15, 0x03, 0x16, 0x03, 0x17, 0x03, + 0x18, 0x03, 0x19, 0x03, 0x1A, 0x03, 0x1B, 0x03, 0x1C, 0x03, 0x1D, 0x03, 0x1E, 0x03, 0x1F, 0x03, + 0x20, 0x03, 0x21, 0x03, 0x22, 0x03, 0x23, 0x03, 0x24, 0x03, 0x25, 0x03, 0x26, 0x03, 0x27, 0x03, + 0x28, 0x03, 0x29, 0x03, 0x2A, 0x03, 0x2B, 0x03, 0x2C, 0x03, 0x2D, 0x03, 0x2E, 0x03, 0x2F, 0x03, + 0x30, 0x03, 0x31, 0x03, 0x32, 0x03, 0x33, 0x03, 0x34, 0x03, 0x35, 0x03, 0x36, 0x03, 0x37, 0x03, + 0x38, 0x03, 0x39, 0x03, 0x3A, 0x03, 0x3B, 0x03, 0x3C, 0x03, 0x3D, 0x03, 0x3E, 0x03, 0x3F, 0x03, + 0x40, 0x03, 0x41, 0x03, 0x42, 0x03, 0x43, 0x03, 0x44, 0x03, 0x45, 0x03, 0x46, 0x03, 0x47, 0x03, + 0x48, 0x03, 0x49, 0x03, 0x4A, 0x03, 0x4B, 0x03, 0x4C, 0x03, 0x4D, 0x03, 0x4E, 0x03, 0x4F, 0x03, + 0x50, 0x03, 0x51, 0x03, 0x52, 0x03, 0x53, 0x03, 0x54, 0x03, 0x55, 0x03, 0x56, 0x03, 0x57, 0x03, + 0x58, 0x03, 0x59, 0x03, 0x5A, 0x03, 0x5B, 0x03, 0x5C, 0x03, 0x5D, 0x03, 0x5E, 0x03, 0x5F, 0x03, + 0x60, 0x03, 0x61, 0x03, 0x62, 0x03, 0x63, 0x03, 0x64, 0x03, 0x65, 0x03, 0x66, 0x03, 0x67, 0x03, + 0x68, 0x03, 0x69, 0x03, 0x6A, 0x03, 0x6B, 0x03, 0x6C, 0x03, 0x6D, 0x03, 0x6E, 0x03, 0x6F, 0x03, + 0x70, 0x03, 0x71, 0x03, 0x72, 0x03, 0x73, 0x03, 0x74, 0x03, 0x75, 0x03, 0x76, 0x03, 0x77, 0x03, + 0x78, 0x03, 0x79, 0x03, 0x7A, 0x03, 0xFD, 0x03, 0xFE, 0x03, 0xFF, 0x03, 0x7E, 0x03, 0x7F, 0x03, + 0x80, 0x03, 0x81, 0x03, 0x82, 0x03, 0x83, 0x03, 0x84, 0x03, 0x85, 0x03, 0x86, 0x03, 0x87, 0x03, + 0x88, 0x03, 0x89, 0x03, 0x8A, 0x03, 0x8B, 0x03, 0x8C, 0x03, 0x8D, 0x03, 0x8E, 0x03, 0x8F, 0x03, + 0x90, 0x03, 0x91, 0x03, 0x92, 0x03, 0x93, 0x03, 0x94, 0x03, 0x95, 0x03, 0x96, 0x03, 0x97, 0x03, + 0x98, 0x03, 0x99, 0x03, 0x9A, 0x03, 0x9B, 0x03, 0x9C, 0x03, 0x9D, 0x03, 0x9E, 0x03, 0x9F, 0x03, + 0xA0, 0x03, 0xA1, 0x03, 0xA2, 0x03, 0xA3, 0x03, 0xA4, 0x03, 0xA5, 0x03, 0xA6, 0x03, 0xA7, 0x03, + 0xA8, 0x03, 0xA9, 0x03, 0xAA, 0x03, 0xAB, 0x03, 0x86, 0x03, 0x88, 0x03, 0x89, 0x03, 0x8A, 0x03, + 0xB0, 0x03, 0x91, 0x03, 0x92, 0x03, 0x93, 0x03, 0x94, 0x03, 0x95, 0x03, 0x96, 0x03, 0x97, 0x03, + 0x98, 0x03, 0x99, 0x03, 0x9A, 0x03, 0x9B, 0x03, 0x9C, 0x03, 0x9D, 0x03, 0x9E, 0x03, 0x9F, 0x03, + 0xA0, 0x03, 0xA1, 0x03, 0xA3, 0x03, 0xA3, 0x03, 0xA4, 0x03, 0xA5, 0x03, 0xA6, 0x03, 0xA7, 0x03, + 0xA8, 0x03, 0xA9, 0x03, 0xAA, 0x03, 0xAB, 0x03, 0x8C, 0x03, 0x8E, 0x03, 0x8F, 0x03, 0xCF, 0x03, + 0xD0, 0x03, 0xD1, 0x03, 0xD2, 0x03, 0xD3, 0x03, 0xD4, 0x03, 0xD5, 0x03, 0xD6, 0x03, 0xD7, 0x03, + 0xD8, 0x03, 0xD8, 0x03, 0xDA, 0x03, 0xDA, 0x03, 0xDC, 0x03, 0xDC, 0x03, 0xDE, 0x03, 0xDE, 0x03, + 0xE0, 0x03, 0xE0, 0x03, 0xE2, 0x03, 0xE2, 0x03, 0xE4, 0x03, 0xE4, 0x03, 0xE6, 0x03, 0xE6, 0x03, + 0xE8, 0x03, 0xE8, 0x03, 0xEA, 0x03, 0xEA, 0x03, 0xEC, 0x03, 0xEC, 0x03, 0xEE, 0x03, 0xEE, 0x03, + 0xF0, 0x03, 0xF1, 0x03, 0xF9, 0x03, 0xF3, 0x03, 0xF4, 0x03, 0xF5, 0x03, 0xF6, 0x03, 0xF7, 0x03, + 0xF7, 0x03, 0xF9, 0x03, 0xFA, 0x03, 0xFA, 0x03, 0xFC, 0x03, 0xFD, 0x03, 0xFE, 0x03, 0xFF, 0x03, + 0x00, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x04, 0x04, 0x04, 0x05, 0x04, 0x06, 0x04, 0x07, 0x04, + 0x08, 0x04, 0x09, 0x04, 0x0A, 0x04, 0x0B, 0x04, 0x0C, 0x04, 0x0D, 0x04, 0x0E, 0x04, 0x0F, 0x04, + 0x10, 0x04, 0x11, 0x04, 0x12, 0x04, 0x13, 0x04, 0x14, 0x04, 0x15, 0x04, 0x16, 0x04, 0x17, 0x04, + 0x18, 0x04, 0x19, 0x04, 0x1A, 0x04, 0x1B, 0x04, 0x1C, 0x04, 0x1D, 0x04, 0x1E, 0x04, 0x1F, 0x04, + 0x20, 0x04, 0x21, 0x04, 0x22, 0x04, 0x23, 0x04, 0x24, 0x04, 0x25, 0x04, 0x26, 0x04, 0x27, 0x04, + 0x28, 0x04, 0x29, 0x04, 0x2A, 0x04, 0x2B, 0x04, 0x2C, 0x04, 0x2D, 0x04, 0x2E, 0x04, 0x2F, 0x04, + 0x10, 0x04, 0x11, 0x04, 0x12, 0x04, 0x13, 0x04, 0x14, 0x04, 0x15, 0x04, 0x16, 0x04, 0x17, 0x04, + 0x18, 0x04, 0x19, 0x04, 0x1A, 0x04, 0x1B, 0x04, 0x1C, 0x04, 0x1D, 0x04, 0x1E, 0x04, 0x1F, 0x04, + 0x20, 0x04, 0x21, 0x04, 0x22, 0x04, 0x23, 0x04, 0x24, 0x04, 0x25, 0x04, 0x26, 0x04, 0x27, 0x04, + 0x28, 0x04, 0x29, 0x04, 0x2A, 0x04, 0x2B, 0x04, 0x2C, 0x04, 0x2D, 0x04, 0x2E, 0x04, 0x2F, 0x04, + 0x00, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x04, 0x04, 0x04, 0x05, 0x04, 0x06, 0x04, 0x07, 0x04, + 0x08, 0x04, 0x09, 0x04, 0x0A, 0x04, 0x0B, 0x04, 0x0C, 0x04, 0x0D, 0x04, 0x0E, 0x04, 0x0F, 0x04, + 0x60, 0x04, 0x60, 0x04, 0x62, 0x04, 0x62, 0x04, 0x64, 0x04, 0x64, 0x04, 0x66, 0x04, 0x66, 0x04, + 0x68, 0x04, 0x68, 0x04, 0x6A, 0x04, 0x6A, 0x04, 0x6C, 0x04, 0x6C, 0x04, 0x6E, 0x04, 0x6E, 0x04, + 0x70, 0x04, 0x70, 0x04, 0x72, 0x04, 0x72, 0x04, 0x74, 0x04, 0x74, 0x04, 0x76, 0x04, 0x76, 0x04, + 0x78, 0x04, 0x78, 0x04, 0x7A, 0x04, 0x7A, 0x04, 0x7C, 0x04, 0x7C, 0x04, 0x7E, 0x04, 0x7E, 0x04, + 0x80, 0x04, 0x80, 0x04, 0x82, 0x04, 0x83, 0x04, 0x84, 0x04, 0x85, 0x04, 0x86, 0x04, 0x87, 0x04, + 0x88, 0x04, 0x89, 0x04, 0x8A, 0x04, 0x8A, 0x04, 0x8C, 0x04, 0x8C, 0x04, 0x8E, 0x04, 0x8E, 0x04, + 0x90, 0x04, 0x90, 0x04, 0x92, 0x04, 0x92, 0x04, 0x94, 0x04, 0x94, 0x04, 0x96, 0x04, 0x96, 0x04, + 0x98, 0x04, 0x98, 0x04, 0x9A, 0x04, 0x9A, 0x04, 0x9C, 0x04, 0x9C, 0x04, 0x9E, 0x04, 0x9E, 0x04, + 0xA0, 0x04, 0xA0, 0x04, 0xA2, 0x04, 0xA2, 0x04, 0xA4, 0x04, 0xA4, 0x04, 0xA6, 0x04, 0xA6, 0x04, + 0xA8, 0x04, 0xA8, 0x04, 0xAA, 0x04, 0xAA, 0x04, 0xAC, 0x04, 0xAC, 0x04, 0xAE, 0x04, 0xAE, 0x04, + 0xB0, 0x04, 0xB0, 0x04, 0xB2, 0x04, 0xB2, 0x04, 0xB4, 0x04, 0xB4, 0x04, 0xB6, 0x04, 0xB6, 0x04, + 0xB8, 0x04, 0xB8, 0x04, 0xBA, 0x04, 0xBA, 0x04, 0xBC, 0x04, 0xBC, 0x04, 0xBE, 0x04, 0xBE, 0x04, + 0xC0, 0x04, 0xC1, 0x04, 0xC1, 0x04, 0xC3, 0x04, 0xC3, 0x04, 0xC5, 0x04, 0xC5, 0x04, 0xC7, 0x04, + 0xC7, 0x04, 0xC9, 0x04, 0xC9, 0x04, 0xCB, 0x04, 0xCB, 0x04, 0xCD, 0x04, 0xCD, 0x04, 0xC0, 0x04, + 0xD0, 0x04, 0xD0, 0x04, 0xD2, 0x04, 0xD2, 0x04, 0xD4, 0x04, 0xD4, 0x04, 0xD6, 0x04, 0xD6, 0x04, + 0xD8, 0x04, 0xD8, 0x04, 0xDA, 0x04, 0xDA, 0x04, 0xDC, 0x04, 0xDC, 0x04, 0xDE, 0x04, 0xDE, 0x04, + 0xE0, 0x04, 0xE0, 0x04, 0xE2, 0x04, 0xE2, 0x04, 0xE4, 0x04, 0xE4, 0x04, 0xE6, 0x04, 0xE6, 0x04, + 0xE8, 0x04, 0xE8, 0x04, 0xEA, 0x04, 0xEA, 0x04, 0xEC, 0x04, 0xEC, 0x04, 0xEE, 0x04, 0xEE, 0x04, + 0xF0, 0x04, 0xF0, 0x04, 0xF2, 0x04, 0xF2, 0x04, 0xF4, 0x04, 0xF4, 0x04, 0xF6, 0x04, 0xF6, 0x04, + 0xF8, 0x04, 0xF8, 0x04, 0xFA, 0x04, 0xFA, 0x04, 0xFC, 0x04, 0xFC, 0x04, 0xFE, 0x04, 0xFE, 0x04, + 0x00, 0x05, 0x00, 0x05, 0x02, 0x05, 0x02, 0x05, 0x04, 0x05, 0x04, 0x05, 0x06, 0x05, 0x06, 0x05, + 0x08, 0x05, 0x08, 0x05, 0x0A, 0x05, 0x0A, 0x05, 0x0C, 0x05, 0x0C, 0x05, 0x0E, 0x05, 0x0E, 0x05, + 0x10, 0x05, 0x10, 0x05, 0x12, 0x05, 0x12, 0x05, 0x14, 0x05, 0x15, 0x05, 0x16, 0x05, 0x17, 0x05, + 0x18, 0x05, 0x19, 0x05, 0x1A, 0x05, 0x1B, 0x05, 0x1C, 0x05, 0x1D, 0x05, 0x1E, 0x05, 0x1F, 0x05, + 0x20, 0x05, 0x21, 0x05, 0x22, 0x05, 0x23, 0x05, 0x24, 0x05, 0x25, 0x05, 0x26, 0x05, 0x27, 0x05, + 0x28, 0x05, 0x29, 0x05, 0x2A, 0x05, 0x2B, 0x05, 0x2C, 0x05, 0x2D, 0x05, 0x2E, 0x05, 0x2F, 0x05, + 0x30, 0x05, 0x31, 0x05, 0x32, 0x05, 0x33, 0x05, 0x34, 0x05, 0x35, 0x05, 0x36, 0x05, 0x37, 0x05, + 0x38, 0x05, 0x39, 0x05, 0x3A, 0x05, 0x3B, 0x05, 0x3C, 0x05, 0x3D, 0x05, 0x3E, 0x05, 0x3F, 0x05, + 0x40, 0x05, 0x41, 0x05, 0x42, 0x05, 0x43, 0x05, 0x44, 0x05, 0x45, 0x05, 0x46, 0x05, 0x47, 0x05, + 0x48, 0x05, 0x49, 0x05, 0x4A, 0x05, 0x4B, 0x05, 0x4C, 0x05, 0x4D, 0x05, 0x4E, 0x05, 0x4F, 0x05, + 0x50, 0x05, 0x51, 0x05, 0x52, 0x05, 0x53, 0x05, 0x54, 0x05, 0x55, 0x05, 0x56, 0x05, 0x57, 0x05, + 0x58, 0x05, 0x59, 0x05, 0x5A, 0x05, 0x5B, 0x05, 0x5C, 0x05, 0x5D, 0x05, 0x5E, 0x05, 0x5F, 0x05, + 0x60, 0x05, 0x31, 0x05, 0x32, 0x05, 0x33, 0x05, 0x34, 0x05, 0x35, 0x05, 0x36, 0x05, 0x37, 0x05, + 0x38, 0x05, 0x39, 0x05, 0x3A, 0x05, 0x3B, 0x05, 0x3C, 0x05, 0x3D, 0x05, 0x3E, 0x05, 0x3F, 0x05, + 0x40, 0x05, 0x41, 0x05, 0x42, 0x05, 0x43, 0x05, 0x44, 0x05, 0x45, 0x05, 0x46, 0x05, 0x47, 0x05, + 0x48, 0x05, 0x49, 0x05, 0x4A, 0x05, 0x4B, 0x05, 0x4C, 0x05, 0x4D, 0x05, 0x4E, 0x05, 0x4F, 0x05, + 0x50, 0x05, 0x51, 0x05, 0x52, 0x05, 0x53, 0x05, 0x54, 0x05, 0x55, 0x05, 0x56, 0x05, 0xFF, 0xFF, + 0xF6, 0x17, 0x63, 0x2C, 0x7E, 0x1D, 0x7F, 0x1D, 0x80, 0x1D, 0x81, 0x1D, 0x82, 0x1D, 0x83, 0x1D, + 0x84, 0x1D, 0x85, 0x1D, 0x86, 0x1D, 0x87, 0x1D, 0x88, 0x1D, 0x89, 0x1D, 0x8A, 0x1D, 0x8B, 0x1D, + 0x8C, 0x1D, 0x8D, 0x1D, 0x8E, 0x1D, 0x8F, 0x1D, 0x90, 0x1D, 0x91, 0x1D, 0x92, 0x1D, 0x93, 0x1D, + 0x94, 0x1D, 0x95, 0x1D, 0x96, 0x1D, 0x97, 0x1D, 0x98, 0x1D, 0x99, 0x1D, 0x9A, 0x1D, 0x9B, 0x1D, + 0x9C, 0x1D, 0x9D, 0x1D, 0x9E, 0x1D, 0x9F, 0x1D, 0xA0, 0x1D, 0xA1, 0x1D, 0xA2, 0x1D, 0xA3, 0x1D, + 0xA4, 0x1D, 0xA5, 0x1D, 0xA6, 0x1D, 0xA7, 0x1D, 0xA8, 0x1D, 0xA9, 0x1D, 0xAA, 0x1D, 0xAB, 0x1D, + 0xAC, 0x1D, 0xAD, 0x1D, 0xAE, 0x1D, 0xAF, 0x1D, 0xB0, 0x1D, 0xB1, 0x1D, 0xB2, 0x1D, 0xB3, 0x1D, + 0xB4, 0x1D, 0xB5, 0x1D, 0xB6, 0x1D, 0xB7, 0x1D, 0xB8, 0x1D, 0xB9, 0x1D, 0xBA, 0x1D, 0xBB, 0x1D, + 0xBC, 0x1D, 0xBD, 0x1D, 0xBE, 0x1D, 0xBF, 0x1D, 0xC0, 0x1D, 0xC1, 0x1D, 0xC2, 0x1D, 0xC3, 0x1D, + 0xC4, 0x1D, 0xC5, 0x1D, 0xC6, 0x1D, 0xC7, 0x1D, 0xC8, 0x1D, 0xC9, 0x1D, 0xCA, 0x1D, 0xCB, 0x1D, + 0xCC, 0x1D, 0xCD, 0x1D, 0xCE, 0x1D, 0xCF, 0x1D, 0xD0, 0x1D, 0xD1, 0x1D, 0xD2, 0x1D, 0xD3, 0x1D, + 0xD4, 0x1D, 0xD5, 0x1D, 0xD6, 0x1D, 0xD7, 0x1D, 0xD8, 0x1D, 0xD9, 0x1D, 0xDA, 0x1D, 0xDB, 0x1D, + 0xDC, 0x1D, 0xDD, 0x1D, 0xDE, 0x1D, 0xDF, 0x1D, 0xE0, 0x1D, 0xE1, 0x1D, 0xE2, 0x1D, 0xE3, 0x1D, + 0xE4, 0x1D, 0xE5, 0x1D, 0xE6, 0x1D, 0xE7, 0x1D, 0xE8, 0x1D, 0xE9, 0x1D, 0xEA, 0x1D, 0xEB, 0x1D, + 0xEC, 0x1D, 0xED, 0x1D, 0xEE, 0x1D, 0xEF, 0x1D, 0xF0, 0x1D, 0xF1, 0x1D, 0xF2, 0x1D, 0xF3, 0x1D, + 0xF4, 0x1D, 0xF5, 0x1D, 0xF6, 0x1D, 0xF7, 0x1D, 0xF8, 0x1D, 0xF9, 0x1D, 0xFA, 0x1D, 0xFB, 0x1D, + 0xFC, 0x1D, 0xFD, 0x1D, 0xFE, 0x1D, 0xFF, 0x1D, 0x00, 0x1E, 0x00, 0x1E, 0x02, 0x1E, 0x02, 0x1E, + 0x04, 0x1E, 0x04, 0x1E, 0x06, 0x1E, 0x06, 0x1E, 0x08, 0x1E, 0x08, 0x1E, 0x0A, 0x1E, 0x0A, 0x1E, + 0x0C, 0x1E, 0x0C, 0x1E, 0x0E, 0x1E, 0x0E, 0x1E, 0x10, 0x1E, 0x10, 0x1E, 0x12, 0x1E, 0x12, 0x1E, + 0x14, 0x1E, 0x14, 0x1E, 0x16, 0x1E, 0x16, 0x1E, 0x18, 0x1E, 0x18, 0x1E, 0x1A, 0x1E, 0x1A, 0x1E, + 0x1C, 0x1E, 0x1C, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x20, 0x1E, 0x20, 0x1E, 0x22, 0x1E, 0x22, 0x1E, + 0x24, 0x1E, 0x24, 0x1E, 0x26, 0x1E, 0x26, 0x1E, 0x28, 0x1E, 0x28, 0x1E, 0x2A, 0x1E, 0x2A, 0x1E, + 0x2C, 0x1E, 0x2C, 0x1E, 0x2E, 0x1E, 0x2E, 0x1E, 0x30, 0x1E, 0x30, 0x1E, 0x32, 0x1E, 0x32, 0x1E, + 0x34, 0x1E, 0x34, 0x1E, 0x36, 0x1E, 0x36, 0x1E, 0x38, 0x1E, 0x38, 0x1E, 0x3A, 0x1E, 0x3A, 0x1E, + 0x3C, 0x1E, 0x3C, 0x1E, 0x3E, 0x1E, 0x3E, 0x1E, 0x40, 0x1E, 0x40, 0x1E, 0x42, 0x1E, 0x42, 0x1E, + 0x44, 0x1E, 0x44, 0x1E, 0x46, 0x1E, 0x46, 0x1E, 0x48, 0x1E, 0x48, 0x1E, 0x4A, 0x1E, 0x4A, 0x1E, + 0x4C, 0x1E, 0x4C, 0x1E, 0x4E, 0x1E, 0x4E, 0x1E, 0x50, 0x1E, 0x50, 0x1E, 0x52, 0x1E, 0x52, 0x1E, + 0x54, 0x1E, 0x54, 0x1E, 0x56, 0x1E, 0x56, 0x1E, 0x58, 0x1E, 0x58, 0x1E, 0x5A, 0x1E, 0x5A, 0x1E, + 0x5C, 0x1E, 0x5C, 0x1E, 0x5E, 0x1E, 0x5E, 0x1E, 0x60, 0x1E, 0x60, 0x1E, 0x62, 0x1E, 0x62, 0x1E, + 0x64, 0x1E, 0x64, 0x1E, 0x66, 0x1E, 0x66, 0x1E, 0x68, 0x1E, 0x68, 0x1E, 0x6A, 0x1E, 0x6A, 0x1E, + 0x6C, 0x1E, 0x6C, 0x1E, 0x6E, 0x1E, 0x6E, 0x1E, 0x70, 0x1E, 0x70, 0x1E, 0x72, 0x1E, 0x72, 0x1E, + 0x74, 0x1E, 0x74, 0x1E, 0x76, 0x1E, 0x76, 0x1E, 0x78, 0x1E, 0x78, 0x1E, 0x7A, 0x1E, 0x7A, 0x1E, + 0x7C, 0x1E, 0x7C, 0x1E, 0x7E, 0x1E, 0x7E, 0x1E, 0x80, 0x1E, 0x80, 0x1E, 0x82, 0x1E, 0x82, 0x1E, + 0x84, 0x1E, 0x84, 0x1E, 0x86, 0x1E, 0x86, 0x1E, 0x88, 0x1E, 0x88, 0x1E, 0x8A, 0x1E, 0x8A, 0x1E, + 0x8C, 0x1E, 0x8C, 0x1E, 0x8E, 0x1E, 0x8E, 0x1E, 0x90, 0x1E, 0x90, 0x1E, 0x92, 0x1E, 0x92, 0x1E, + 0x94, 0x1E, 0x94, 0x1E, 0x96, 0x1E, 0x97, 0x1E, 0x98, 0x1E, 0x99, 0x1E, 0x9A, 0x1E, 0x9B, 0x1E, + 0x9C, 0x1E, 0x9D, 0x1E, 0x9E, 0x1E, 0x9F, 0x1E, 0xA0, 0x1E, 0xA0, 0x1E, 0xA2, 0x1E, 0xA2, 0x1E, + 0xA4, 0x1E, 0xA4, 0x1E, 0xA6, 0x1E, 0xA6, 0x1E, 0xA8, 0x1E, 0xA8, 0x1E, 0xAA, 0x1E, 0xAA, 0x1E, + 0xAC, 0x1E, 0xAC, 0x1E, 0xAE, 0x1E, 0xAE, 0x1E, 0xB0, 0x1E, 0xB0, 0x1E, 0xB2, 0x1E, 0xB2, 0x1E, + 0xB4, 0x1E, 0xB4, 0x1E, 0xB6, 0x1E, 0xB6, 0x1E, 0xB8, 0x1E, 0xB8, 0x1E, 0xBA, 0x1E, 0xBA, 0x1E, + 0xBC, 0x1E, 0xBC, 0x1E, 0xBE, 0x1E, 0xBE, 0x1E, 0xC0, 0x1E, 0xC0, 0x1E, 0xC2, 0x1E, 0xC2, 0x1E, + 0xC4, 0x1E, 0xC4, 0x1E, 0xC6, 0x1E, 0xC6, 0x1E, 0xC8, 0x1E, 0xC8, 0x1E, 0xCA, 0x1E, 0xCA, 0x1E, + 0xCC, 0x1E, 0xCC, 0x1E, 0xCE, 0x1E, 0xCE, 0x1E, 0xD0, 0x1E, 0xD0, 0x1E, 0xD2, 0x1E, 0xD2, 0x1E, + 0xD4, 0x1E, 0xD4, 0x1E, 0xD6, 0x1E, 0xD6, 0x1E, 0xD8, 0x1E, 0xD8, 0x1E, 0xDA, 0x1E, 0xDA, 0x1E, + 0xDC, 0x1E, 0xDC, 0x1E, 0xDE, 0x1E, 0xDE, 0x1E, 0xE0, 0x1E, 0xE0, 0x1E, 0xE2, 0x1E, 0xE2, 0x1E, + 0xE4, 0x1E, 0xE4, 0x1E, 0xE6, 0x1E, 0xE6, 0x1E, 0xE8, 0x1E, 0xE8, 0x1E, 0xEA, 0x1E, 0xEA, 0x1E, + 0xEC, 0x1E, 0xEC, 0x1E, 0xEE, 0x1E, 0xEE, 0x1E, 0xF0, 0x1E, 0xF0, 0x1E, 0xF2, 0x1E, 0xF2, 0x1E, + 0xF4, 0x1E, 0xF4, 0x1E, 0xF6, 0x1E, 0xF6, 0x1E, 0xF8, 0x1E, 0xF8, 0x1E, 0xFA, 0x1E, 0xFB, 0x1E, + 0xFC, 0x1E, 0xFD, 0x1E, 0xFE, 0x1E, 0xFF, 0x1E, 0x08, 0x1F, 0x09, 0x1F, 0x0A, 0x1F, 0x0B, 0x1F, + 0x0C, 0x1F, 0x0D, 0x1F, 0x0E, 0x1F, 0x0F, 0x1F, 0x08, 0x1F, 0x09, 0x1F, 0x0A, 0x1F, 0x0B, 0x1F, + 0x0C, 0x1F, 0x0D, 0x1F, 0x0E, 0x1F, 0x0F, 0x1F, 0x18, 0x1F, 0x19, 0x1F, 0x1A, 0x1F, 0x1B, 0x1F, + 0x1C, 0x1F, 0x1D, 0x1F, 0x16, 0x1F, 0x17, 0x1F, 0x18, 0x1F, 0x19, 0x1F, 0x1A, 0x1F, 0x1B, 0x1F, + 0x1C, 0x1F, 0x1D, 0x1F, 0x1E, 0x1F, 0x1F, 0x1F, 0x28, 0x1F, 0x29, 0x1F, 0x2A, 0x1F, 0x2B, 0x1F, + 0x2C, 0x1F, 0x2D, 0x1F, 0x2E, 0x1F, 0x2F, 0x1F, 0x28, 0x1F, 0x29, 0x1F, 0x2A, 0x1F, 0x2B, 0x1F, + 0x2C, 0x1F, 0x2D, 0x1F, 0x2E, 0x1F, 0x2F, 0x1F, 0x38, 0x1F, 0x39, 0x1F, 0x3A, 0x1F, 0x3B, 0x1F, + 0x3C, 0x1F, 0x3D, 0x1F, 0x3E, 0x1F, 0x3F, 0x1F, 0x38, 0x1F, 0x39, 0x1F, 0x3A, 0x1F, 0x3B, 0x1F, + 0x3C, 0x1F, 0x3D, 0x1F, 0x3E, 0x1F, 0x3F, 0x1F, 0x48, 0x1F, 0x49, 0x1F, 0x4A, 0x1F, 0x4B, 0x1F, + 0x4C, 0x1F, 0x4D, 0x1F, 0x46, 0x1F, 0x47, 0x1F, 0x48, 0x1F, 0x49, 0x1F, 0x4A, 0x1F, 0x4B, 0x1F, + 0x4C, 0x1F, 0x4D, 0x1F, 0x4E, 0x1F, 0x4F, 0x1F, 0x50, 0x1F, 0x59, 0x1F, 0x52, 0x1F, 0x5B, 0x1F, + 0x54, 0x1F, 0x5D, 0x1F, 0x56, 0x1F, 0x5F, 0x1F, 0x58, 0x1F, 0x59, 0x1F, 0x5A, 0x1F, 0x5B, 0x1F, + 0x5C, 0x1F, 0x5D, 0x1F, 0x5E, 0x1F, 0x5F, 0x1F, 0x68, 0x1F, 0x69, 0x1F, 0x6A, 0x1F, 0x6B, 0x1F, + 0x6C, 0x1F, 0x6D, 0x1F, 0x6E, 0x1F, 0x6F, 0x1F, 0x68, 0x1F, 0x69, 0x1F, 0x6A, 0x1F, 0x6B, 0x1F, + 0x6C, 0x1F, 0x6D, 0x1F, 0x6E, 0x1F, 0x6F, 0x1F, 0xBA, 0x1F, 0xBB, 0x1F, 0xC8, 0x1F, 0xC9, 0x1F, + 0xCA, 0x1F, 0xCB, 0x1F, 0xDA, 0x1F, 0xDB, 0x1F, 0xF8, 0x1F, 0xF9, 0x1F, 0xEA, 0x1F, 0xEB, 0x1F, + 0xFA, 0x1F, 0xFB, 0x1F, 0x7E, 0x1F, 0x7F, 0x1F, 0x88, 0x1F, 0x89, 0x1F, 0x8A, 0x1F, 0x8B, 0x1F, + 0x8C, 0x1F, 0x8D, 0x1F, 0x8E, 0x1F, 0x8F, 0x1F, 0x88, 0x1F, 0x89, 0x1F, 0x8A, 0x1F, 0x8B, 0x1F, + 0x8C, 0x1F, 0x8D, 0x1F, 0x8E, 0x1F, 0x8F, 0x1F, 0x98, 0x1F, 0x99, 0x1F, 0x9A, 0x1F, 0x9B, 0x1F, + 0x9C, 0x1F, 0x9D, 0x1F, 0x9E, 0x1F, 0x9F, 0x1F, 0x98, 0x1F, 0x99, 0x1F, 0x9A, 0x1F, 0x9B, 0x1F, + 0x9C, 0x1F, 0x9D, 0x1F, 0x9E, 0x1F, 0x9F, 0x1F, 0xA8, 0x1F, 0xA9, 0x1F, 0xAA, 0x1F, 0xAB, 0x1F, + 0xAC, 0x1F, 0xAD, 0x1F, 0xAE, 0x1F, 0xAF, 0x1F, 0xA8, 0x1F, 0xA9, 0x1F, 0xAA, 0x1F, 0xAB, 0x1F, + 0xAC, 0x1F, 0xAD, 0x1F, 0xAE, 0x1F, 0xAF, 0x1F, 0xB8, 0x1F, 0xB9, 0x1F, 0xB2, 0x1F, 0xBC, 0x1F, + 0xB4, 0x1F, 0xB5, 0x1F, 0xB6, 0x1F, 0xB7, 0x1F, 0xB8, 0x1F, 0xB9, 0x1F, 0xBA, 0x1F, 0xBB, 0x1F, + 0xBC, 0x1F, 0xBD, 0x1F, 0xBE, 0x1F, 0xBF, 0x1F, 0xC0, 0x1F, 0xC1, 0x1F, 0xC2, 0x1F, 0xC3, 0x1F, + 0xC4, 0x1F, 0xC5, 0x1F, 0xC6, 0x1F, 0xC7, 0x1F, 0xC8, 0x1F, 0xC9, 0x1F, 0xCA, 0x1F, 0xCB, 0x1F, + 0xC3, 0x1F, 0xCD, 0x1F, 0xCE, 0x1F, 0xCF, 0x1F, 0xD8, 0x1F, 0xD9, 0x1F, 0xD2, 0x1F, 0xD3, 0x1F, + 0xD4, 0x1F, 0xD5, 0x1F, 0xD6, 0x1F, 0xD7, 0x1F, 0xD8, 0x1F, 0xD9, 0x1F, 0xDA, 0x1F, 0xDB, 0x1F, + 0xDC, 0x1F, 0xDD, 0x1F, 0xDE, 0x1F, 0xDF, 0x1F, 0xE8, 0x1F, 0xE9, 0x1F, 0xE2, 0x1F, 0xE3, 0x1F, + 0xE4, 0x1F, 0xEC, 0x1F, 0xE6, 0x1F, 0xE7, 0x1F, 0xE8, 0x1F, 0xE9, 0x1F, 0xEA, 0x1F, 0xEB, 0x1F, + 0xEC, 0x1F, 0xED, 0x1F, 0xEE, 0x1F, 0xEF, 0x1F, 0xF0, 0x1F, 0xF1, 0x1F, 0xF2, 0x1F, 0xF3, 0x1F, + 0xF4, 0x1F, 0xF5, 0x1F, 0xF6, 0x1F, 0xF7, 0x1F, 0xF8, 0x1F, 0xF9, 0x1F, 0xFA, 0x1F, 0xFB, 0x1F, + 0xF3, 0x1F, 0xFD, 0x1F, 0xFE, 0x1F, 0xFF, 0x1F, 0x00, 0x20, 0x01, 0x20, 0x02, 0x20, 0x03, 0x20, + 0x04, 0x20, 0x05, 0x20, 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x09, 0x20, 0x0A, 0x20, 0x0B, 0x20, + 0x0C, 0x20, 0x0D, 0x20, 0x0E, 0x20, 0x0F, 0x20, 0x10, 0x20, 0x11, 0x20, 0x12, 0x20, 0x13, 0x20, + 0x14, 0x20, 0x15, 0x20, 0x16, 0x20, 0x17, 0x20, 0x18, 0x20, 0x19, 0x20, 0x1A, 0x20, 0x1B, 0x20, + 0x1C, 0x20, 0x1D, 0x20, 0x1E, 0x20, 0x1F, 0x20, 0x20, 0x20, 0x21, 0x20, 0x22, 0x20, 0x23, 0x20, + 0x24, 0x20, 0x25, 0x20, 0x26, 0x20, 0x27, 0x20, 0x28, 0x20, 0x29, 0x20, 0x2A, 0x20, 0x2B, 0x20, + 0x2C, 0x20, 0x2D, 0x20, 0x2E, 0x20, 0x2F, 0x20, 0x30, 0x20, 0x31, 0x20, 0x32, 0x20, 0x33, 0x20, + 0x34, 0x20, 0x35, 0x20, 0x36, 0x20, 0x37, 0x20, 0x38, 0x20, 0x39, 0x20, 0x3A, 0x20, 0x3B, 0x20, + 0x3C, 0x20, 0x3D, 0x20, 0x3E, 0x20, 0x3F, 0x20, 0x40, 0x20, 0x41, 0x20, 0x42, 0x20, 0x43, 0x20, + 0x44, 0x20, 0x45, 0x20, 0x46, 0x20, 0x47, 0x20, 0x48, 0x20, 0x49, 0x20, 0x4A, 0x20, 0x4B, 0x20, + 0x4C, 0x20, 0x4D, 0x20, 0x4E, 0x20, 0x4F, 0x20, 0x50, 0x20, 0x51, 0x20, 0x52, 0x20, 0x53, 0x20, + 0x54, 0x20, 0x55, 0x20, 0x56, 0x20, 0x57, 0x20, 0x58, 0x20, 0x59, 0x20, 0x5A, 0x20, 0x5B, 0x20, + 0x5C, 0x20, 0x5D, 0x20, 0x5E, 0x20, 0x5F, 0x20, 0x60, 0x20, 0x61, 0x20, 0x62, 0x20, 0x63, 0x20, + 0x64, 0x20, 0x65, 0x20, 0x66, 0x20, 0x67, 0x20, 0x68, 0x20, 0x69, 0x20, 0x6A, 0x20, 0x6B, 0x20, + 0x6C, 0x20, 0x6D, 0x20, 0x6E, 0x20, 0x6F, 0x20, 0x70, 0x20, 0x71, 0x20, 0x72, 0x20, 0x73, 0x20, + 0x74, 0x20, 0x75, 0x20, 0x76, 0x20, 0x77, 0x20, 0x78, 0x20, 0x79, 0x20, 0x7A, 0x20, 0x7B, 0x20, + 0x7C, 0x20, 0x7D, 0x20, 0x7E, 0x20, 0x7F, 0x20, 0x80, 0x20, 0x81, 0x20, 0x82, 0x20, 0x83, 0x20, + 0x84, 0x20, 0x85, 0x20, 0x86, 0x20, 0x87, 0x20, 0x88, 0x20, 0x89, 0x20, 0x8A, 0x20, 0x8B, 0x20, + 0x8C, 0x20, 0x8D, 0x20, 0x8E, 0x20, 0x8F, 0x20, 0x90, 0x20, 0x91, 0x20, 0x92, 0x20, 0x93, 0x20, + 0x94, 0x20, 0x95, 0x20, 0x96, 0x20, 0x97, 0x20, 0x98, 0x20, 0x99, 0x20, 0x9A, 0x20, 0x9B, 0x20, + 0x9C, 0x20, 0x9D, 0x20, 0x9E, 0x20, 0x9F, 0x20, 0xA0, 0x20, 0xA1, 0x20, 0xA2, 0x20, 0xA3, 0x20, + 0xA4, 0x20, 0xA5, 0x20, 0xA6, 0x20, 0xA7, 0x20, 0xA8, 0x20, 0xA9, 0x20, 0xAA, 0x20, 0xAB, 0x20, + 0xAC, 0x20, 0xAD, 0x20, 0xAE, 0x20, 0xAF, 0x20, 0xB0, 0x20, 0xB1, 0x20, 0xB2, 0x20, 0xB3, 0x20, + 0xB4, 0x20, 0xB5, 0x20, 0xB6, 0x20, 0xB7, 0x20, 0xB8, 0x20, 0xB9, 0x20, 0xBA, 0x20, 0xBB, 0x20, + 0xBC, 0x20, 0xBD, 0x20, 0xBE, 0x20, 0xBF, 0x20, 0xC0, 0x20, 0xC1, 0x20, 0xC2, 0x20, 0xC3, 0x20, + 0xC4, 0x20, 0xC5, 0x20, 0xC6, 0x20, 0xC7, 0x20, 0xC8, 0x20, 0xC9, 0x20, 0xCA, 0x20, 0xCB, 0x20, + 0xCC, 0x20, 0xCD, 0x20, 0xCE, 0x20, 0xCF, 0x20, 0xD0, 0x20, 0xD1, 0x20, 0xD2, 0x20, 0xD3, 0x20, + 0xD4, 0x20, 0xD5, 0x20, 0xD6, 0x20, 0xD7, 0x20, 0xD8, 0x20, 0xD9, 0x20, 0xDA, 0x20, 0xDB, 0x20, + 0xDC, 0x20, 0xDD, 0x20, 0xDE, 0x20, 0xDF, 0x20, 0xE0, 0x20, 0xE1, 0x20, 0xE2, 0x20, 0xE3, 0x20, + 0xE4, 0x20, 0xE5, 0x20, 0xE6, 0x20, 0xE7, 0x20, 0xE8, 0x20, 0xE9, 0x20, 0xEA, 0x20, 0xEB, 0x20, + 0xEC, 0x20, 0xED, 0x20, 0xEE, 0x20, 0xEF, 0x20, 0xF0, 0x20, 0xF1, 0x20, 0xF2, 0x20, 0xF3, 0x20, + 0xF4, 0x20, 0xF5, 0x20, 0xF6, 0x20, 0xF7, 0x20, 0xF8, 0x20, 0xF9, 0x20, 0xFA, 0x20, 0xFB, 0x20, + 0xFC, 0x20, 0xFD, 0x20, 0xFE, 0x20, 0xFF, 0x20, 0x00, 0x21, 0x01, 0x21, 0x02, 0x21, 0x03, 0x21, + 0x04, 0x21, 0x05, 0x21, 0x06, 0x21, 0x07, 0x21, 0x08, 0x21, 0x09, 0x21, 0x0A, 0x21, 0x0B, 0x21, + 0x0C, 0x21, 0x0D, 0x21, 0x0E, 0x21, 0x0F, 0x21, 0x10, 0x21, 0x11, 0x21, 0x12, 0x21, 0x13, 0x21, + 0x14, 0x21, 0x15, 0x21, 0x16, 0x21, 0x17, 0x21, 0x18, 0x21, 0x19, 0x21, 0x1A, 0x21, 0x1B, 0x21, + 0x1C, 0x21, 0x1D, 0x21, 0x1E, 0x21, 0x1F, 0x21, 0x20, 0x21, 0x21, 0x21, 0x22, 0x21, 0x23, 0x21, + 0x24, 0x21, 0x25, 0x21, 0x26, 0x21, 0x27, 0x21, 0x28, 0x21, 0x29, 0x21, 0x2A, 0x21, 0x2B, 0x21, + 0x2C, 0x21, 0x2D, 0x21, 0x2E, 0x21, 0x2F, 0x21, 0x30, 0x21, 0x31, 0x21, 0x32, 0x21, 0x33, 0x21, + 0x34, 0x21, 0x35, 0x21, 0x36, 0x21, 0x37, 0x21, 0x38, 0x21, 0x39, 0x21, 0x3A, 0x21, 0x3B, 0x21, + 0x3C, 0x21, 0x3D, 0x21, 0x3E, 0x21, 0x3F, 0x21, 0x40, 0x21, 0x41, 0x21, 0x42, 0x21, 0x43, 0x21, + 0x44, 0x21, 0x45, 0x21, 0x46, 0x21, 0x47, 0x21, 0x48, 0x21, 0x49, 0x21, 0x4A, 0x21, 0x4B, 0x21, + 0x4C, 0x21, 0x4D, 0x21, 0x32, 0x21, 0x4F, 0x21, 0x50, 0x21, 0x51, 0x21, 0x52, 0x21, 0x53, 0x21, + 0x54, 0x21, 0x55, 0x21, 0x56, 0x21, 0x57, 0x21, 0x58, 0x21, 0x59, 0x21, 0x5A, 0x21, 0x5B, 0x21, + 0x5C, 0x21, 0x5D, 0x21, 0x5E, 0x21, 0x5F, 0x21, 0x60, 0x21, 0x61, 0x21, 0x62, 0x21, 0x63, 0x21, + 0x64, 0x21, 0x65, 0x21, 0x66, 0x21, 0x67, 0x21, 0x68, 0x21, 0x69, 0x21, 0x6A, 0x21, 0x6B, 0x21, + 0x6C, 0x21, 0x6D, 0x21, 0x6E, 0x21, 0x6F, 0x21, 0x60, 0x21, 0x61, 0x21, 0x62, 0x21, 0x63, 0x21, + 0x64, 0x21, 0x65, 0x21, 0x66, 0x21, 0x67, 0x21, 0x68, 0x21, 0x69, 0x21, 0x6A, 0x21, 0x6B, 0x21, + 0x6C, 0x21, 0x6D, 0x21, 0x6E, 0x21, 0x6F, 0x21, 0x80, 0x21, 0x81, 0x21, 0x82, 0x21, 0x83, 0x21, + 0x83, 0x21, 0xFF, 0xFF, 0x4B, 0x03, 0xB6, 0x24, 0xB7, 0x24, 0xB8, 0x24, 0xB9, 0x24, 0xBA, 0x24, + 0xBB, 0x24, 0xBC, 0x24, 0xBD, 0x24, 0xBE, 0x24, 0xBF, 0x24, 0xC0, 0x24, 0xC1, 0x24, 0xC2, 0x24, + 0xC3, 0x24, 0xC4, 0x24, 0xC5, 0x24, 0xC6, 0x24, 0xC7, 0x24, 0xC8, 0x24, 0xC9, 0x24, 0xCA, 0x24, + 0xCB, 0x24, 0xCC, 0x24, 0xCD, 0x24, 0xCE, 0x24, 0xCF, 0x24, 0xFF, 0xFF, 0x46, 0x07, 0x00, 0x2C, + 0x01, 0x2C, 0x02, 0x2C, 0x03, 0x2C, 0x04, 0x2C, 0x05, 0x2C, 0x06, 0x2C, 0x07, 0x2C, 0x08, 0x2C, + 0x09, 0x2C, 0x0A, 0x2C, 0x0B, 0x2C, 0x0C, 0x2C, 0x0D, 0x2C, 0x0E, 0x2C, 0x0F, 0x2C, 0x10, 0x2C, + 0x11, 0x2C, 0x12, 0x2C, 0x13, 0x2C, 0x14, 0x2C, 0x15, 0x2C, 0x16, 0x2C, 0x17, 0x2C, 0x18, 0x2C, + 0x19, 0x2C, 0x1A, 0x2C, 0x1B, 0x2C, 0x1C, 0x2C, 0x1D, 0x2C, 0x1E, 0x2C, 0x1F, 0x2C, 0x20, 0x2C, + 0x21, 0x2C, 0x22, 0x2C, 0x23, 0x2C, 0x24, 0x2C, 0x25, 0x2C, 0x26, 0x2C, 0x27, 0x2C, 0x28, 0x2C, + 0x29, 0x2C, 0x2A, 0x2C, 0x2B, 0x2C, 0x2C, 0x2C, 0x2D, 0x2C, 0x2E, 0x2C, 0x5F, 0x2C, 0x60, 0x2C, + 0x60, 0x2C, 0x62, 0x2C, 0x63, 0x2C, 0x64, 0x2C, 0x65, 0x2C, 0x66, 0x2C, 0x67, 0x2C, 0x67, 0x2C, + 0x69, 0x2C, 0x69, 0x2C, 0x6B, 0x2C, 0x6B, 0x2C, 0x6D, 0x2C, 0x6E, 0x2C, 0x6F, 0x2C, 0x70, 0x2C, + 0x71, 0x2C, 0x72, 0x2C, 0x73, 0x2C, 0x74, 0x2C, 0x75, 0x2C, 0x75, 0x2C, 0x77, 0x2C, 0x78, 0x2C, + 0x79, 0x2C, 0x7A, 0x2C, 0x7B, 0x2C, 0x7C, 0x2C, 0x7D, 0x2C, 0x7E, 0x2C, 0x7F, 0x2C, 0x80, 0x2C, + 0x80, 0x2C, 0x82, 0x2C, 0x82, 0x2C, 0x84, 0x2C, 0x84, 0x2C, 0x86, 0x2C, 0x86, 0x2C, 0x88, 0x2C, + 0x88, 0x2C, 0x8A, 0x2C, 0x8A, 0x2C, 0x8C, 0x2C, 0x8C, 0x2C, 0x8E, 0x2C, 0x8E, 0x2C, 0x90, 0x2C, + 0x90, 0x2C, 0x92, 0x2C, 0x92, 0x2C, 0x94, 0x2C, 0x94, 0x2C, 0x96, 0x2C, 0x96, 0x2C, 0x98, 0x2C, + 0x98, 0x2C, 0x9A, 0x2C, 0x9A, 0x2C, 0x9C, 0x2C, 0x9C, 0x2C, 0x9E, 0x2C, 0x9E, 0x2C, 0xA0, 0x2C, + 0xA0, 0x2C, 0xA2, 0x2C, 0xA2, 0x2C, 0xA4, 0x2C, 0xA4, 0x2C, 0xA6, 0x2C, 0xA6, 0x2C, 0xA8, 0x2C, + 0xA8, 0x2C, 0xAA, 0x2C, 0xAA, 0x2C, 0xAC, 0x2C, 0xAC, 0x2C, 0xAE, 0x2C, 0xAE, 0x2C, 0xB0, 0x2C, + 0xB0, 0x2C, 0xB2, 0x2C, 0xB2, 0x2C, 0xB4, 0x2C, 0xB4, 0x2C, 0xB6, 0x2C, 0xB6, 0x2C, 0xB8, 0x2C, + 0xB8, 0x2C, 0xBA, 0x2C, 0xBA, 0x2C, 0xBC, 0x2C, 0xBC, 0x2C, 0xBE, 0x2C, 0xBE, 0x2C, 0xC0, 0x2C, + 0xC0, 0x2C, 0xC2, 0x2C, 0xC2, 0x2C, 0xC4, 0x2C, 0xC4, 0x2C, 0xC6, 0x2C, 0xC6, 0x2C, 0xC8, 0x2C, + 0xC8, 0x2C, 0xCA, 0x2C, 0xCA, 0x2C, 0xCC, 0x2C, 0xCC, 0x2C, 0xCE, 0x2C, 0xCE, 0x2C, 0xD0, 0x2C, + 0xD0, 0x2C, 0xD2, 0x2C, 0xD2, 0x2C, 0xD4, 0x2C, 0xD4, 0x2C, 0xD6, 0x2C, 0xD6, 0x2C, 0xD8, 0x2C, + 0xD8, 0x2C, 0xDA, 0x2C, 0xDA, 0x2C, 0xDC, 0x2C, 0xDC, 0x2C, 0xDE, 0x2C, 0xDE, 0x2C, 0xE0, 0x2C, + 0xE0, 0x2C, 0xE2, 0x2C, 0xE2, 0x2C, 0xE4, 0x2C, 0xE5, 0x2C, 0xE6, 0x2C, 0xE7, 0x2C, 0xE8, 0x2C, + 0xE9, 0x2C, 0xEA, 0x2C, 0xEB, 0x2C, 0xEC, 0x2C, 0xED, 0x2C, 0xEE, 0x2C, 0xEF, 0x2C, 0xF0, 0x2C, + 0xF1, 0x2C, 0xF2, 0x2C, 0xF3, 0x2C, 0xF4, 0x2C, 0xF5, 0x2C, 0xF6, 0x2C, 0xF7, 0x2C, 0xF8, 0x2C, + 0xF9, 0x2C, 0xFA, 0x2C, 0xFB, 0x2C, 0xFC, 0x2C, 0xFD, 0x2C, 0xFE, 0x2C, 0xFF, 0x2C, 0xA0, 0x10, + 0xA1, 0x10, 0xA2, 0x10, 0xA3, 0x10, 0xA4, 0x10, 0xA5, 0x10, 0xA6, 0x10, 0xA7, 0x10, 0xA8, 0x10, + 0xA9, 0x10, 0xAA, 0x10, 0xAB, 0x10, 0xAC, 0x10, 0xAD, 0x10, 0xAE, 0x10, 0xAF, 0x10, 0xB0, 0x10, + 0xB1, 0x10, 0xB2, 0x10, 0xB3, 0x10, 0xB4, 0x10, 0xB5, 0x10, 0xB6, 0x10, 0xB7, 0x10, 0xB8, 0x10, + 0xB9, 0x10, 0xBA, 0x10, 0xBB, 0x10, 0xBC, 0x10, 0xBD, 0x10, 0xBE, 0x10, 0xBF, 0x10, 0xC0, 0x10, + 0xC1, 0x10, 0xC2, 0x10, 0xC3, 0x10, 0xC4, 0x10, 0xC5, 0x10, 0xFF, 0xFF, 0x1B, 0xD2, 0x21, 0xFF, + 0x22, 0xFF, 0x23, 0xFF, 0x24, 0xFF, 0x25, 0xFF, 0x26, 0xFF, 0x27, 0xFF, 0x28, 0xFF, 0x29, 0xFF, + 0x2A, 0xFF, 0x2B, 0xFF, 0x2C, 0xFF, 0x2D, 0xFF, 0x2E, 0xFF, 0x2F, 0xFF, 0x30, 0xFF, 0x31, 0xFF, + 0x32, 0xFF, 0x33, 0xFF, 0x34, 0xFF, 0x35, 0xFF, 0x36, 0xFF, 0x37, 0xFF, 0x38, 0xFF, 0x39, 0xFF, + 0x3A, 0xFF, 0x5B, 0xFF, 0x5C, 0xFF, 0x5D, 0xFF, 0x5E, 0xFF, 0x5F, 0xFF, 0x60, 0xFF, 0x61, 0xFF, + 0x62, 0xFF, 0x63, 0xFF, 0x64, 0xFF, 0x65, 0xFF, 0x66, 0xFF, 0x67, 0xFF, 0x68, 0xFF, 0x69, 0xFF, + 0x6A, 0xFF, 0x6B, 0xFF, 0x6C, 0xFF, 0x6D, 0xFF, 0x6E, 0xFF, 0x6F, 0xFF, 0x70, 0xFF, 0x71, 0xFF, + 0x72, 0xFF, 0x73, 0xFF, 0x74, 0xFF, 0x75, 0xFF, 0x76, 0xFF, 0x77, 0xFF, 0x78, 0xFF, 0x79, 0xFF, + 0x7A, 0xFF, 0x7B, 0xFF, 0x7C, 0xFF, 0x7D, 0xFF, 0x7E, 0xFF, 0x7F, 0xFF, 0x80, 0xFF, 0x81, 0xFF, + 0x82, 0xFF, 0x83, 0xFF, 0x84, 0xFF, 0x85, 0xFF, 0x86, 0xFF, 0x87, 0xFF, 0x88, 0xFF, 0x89, 0xFF, + 0x8A, 0xFF, 0x8B, 0xFF, 0x8C, 0xFF, 0x8D, 0xFF, 0x8E, 0xFF, 0x8F, 0xFF, 0x90, 0xFF, 0x91, 0xFF, + 0x92, 0xFF, 0x93, 0xFF, 0x94, 0xFF, 0x95, 0xFF, 0x96, 0xFF, 0x97, 0xFF, 0x98, 0xFF, 0x99, 0xFF, + 0x9A, 0xFF, 0x9B, 0xFF, 0x9C, 0xFF, 0x9D, 0xFF, 0x9E, 0xFF, 0x9F, 0xFF, 0xA0, 0xFF, 0xA1, 0xFF, + 0xA2, 0xFF, 0xA3, 0xFF, 0xA4, 0xFF, 0xA5, 0xFF, 0xA6, 0xFF, 0xA7, 0xFF, 0xA8, 0xFF, 0xA9, 0xFF, + 0xAA, 0xFF, 0xAB, 0xFF, 0xAC, 0xFF, 0xAD, 0xFF, 0xAE, 0xFF, 0xAF, 0xFF, 0xB0, 0xFF, 0xB1, 0xFF, + 0xB2, 0xFF, 0xB3, 0xFF, 0xB4, 0xFF, 0xB5, 0xFF, 0xB6, 0xFF, 0xB7, 0xFF, 0xB8, 0xFF, 0xB9, 0xFF, + 0xBA, 0xFF, 0xBB, 0xFF, 0xBC, 0xFF, 0xBD, 0xFF, 0xBE, 0xFF, 0xBF, 0xFF, 0xC0, 0xFF, 0xC1, 0xFF, + 0xC2, 0xFF, 0xC3, 0xFF, 0xC4, 0xFF, 0xC5, 0xFF, 0xC6, 0xFF, 0xC7, 0xFF, 0xC8, 0xFF, 0xC9, 0xFF, + 0xCA, 0xFF, 0xCB, 0xFF, 0xCC, 0xFF, 0xCD, 0xFF, 0xCE, 0xFF, 0xCF, 0xFF, 0xD0, 0xFF, 0xD1, 0xFF, + 0xD2, 0xFF, 0xD3, 0xFF, 0xD4, 0xFF, 0xD5, 0xFF, 0xD6, 0xFF, 0xD7, 0xFF, 0xD8, 0xFF, 0xD9, 0xFF, + 0xDA, 0xFF, 0xDB, 0xFF, 0xDC, 0xFF, 0xDD, 0xFF, 0xDE, 0xFF, 0xDF, 0xFF, 0xE0, 0xFF, 0xE1, 0xFF, + 0xE2, 0xFF, 0xE3, 0xFF, 0xE4, 0xFF, 0xE5, 0xFF, 0xE6, 0xFF, 0xE7, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, + 0xEA, 0xFF, 0xEB, 0xFF, 0xEC, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF, + 0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xFF, + 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF +}; + +#endif /* _UPCASE_H */ diff --git a/fs/sdfat/version.h b/fs/sdfat/version.h new file mode 100644 index 0000000000000..c49175c0228bb --- /dev/null +++ b/fs/sdfat/version.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : version.h */ +/* PURPOSE : sdFAT File Manager */ +/* */ +/************************************************************************/ +#define SDFAT_VERSION "2.0.6-lineage" diff --git a/fs/sdfat/xattr.c b/fs/sdfat/xattr.c new file mode 100644 index 0000000000000..40bb850711beb --- /dev/null +++ b/fs/sdfat/xattr.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/************************************************************************/ +/* */ +/* PROJECT : exFAT & FAT12/16/32 File System */ +/* FILE : xattr.c */ +/* PURPOSE : sdFAT code for supporting xattr(Extended File Attributes) */ +/* */ +/*----------------------------------------------------------------------*/ +/* NOTES */ +/* */ +/* */ +/************************************************************************/ + +#include +#include +#include +#include +#include "sdfat.h" + +#ifndef CONFIG_SDFAT_VIRTUAL_XATTR_SELINUX_LABEL +#define CONFIG_SDFAT_VIRTUAL_XATTR_SELINUX_LABEL ("undefined") +#endif + +static const char default_xattr[] = CONFIG_SDFAT_VIRTUAL_XATTR_SELINUX_LABEL; + +static int can_support(const char *name) +{ + if (!name || strcmp(name, "security.selinux")) + return -1; + return 0; +} + +ssize_t sdfat_listxattr(struct dentry *dentry, char *list, size_t size) +{ + return 0; +} + + +/************************************************************************* + * INNER FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY + *************************************************************************/ +static int __sdfat_xattr_check_support(const char *name) +{ + if (can_support(name)) + return -EOPNOTSUPP; + + return 0; +} + +ssize_t __sdfat_getxattr(const char *name, void *value, size_t size) +{ + if (can_support(name)) + return -EOPNOTSUPP; + + if ((size > strlen(default_xattr)+1) && value) + strcpy(value, default_xattr); + + return strlen(default_xattr); +} + + +/************************************************************************* + * FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY + *************************************************************************/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +static int sdfat_xattr_get(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, void *buffer, size_t size) +{ + return __sdfat_getxattr(name, buffer, size); +} + +static int sdfat_xattr_set(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, const void *value, size_t size, + int flags) +{ + return __sdfat_xattr_check_support(name); +} + +const struct xattr_handler sdfat_xattr_handler = { + .prefix = "", /* match anything */ + .get = sdfat_xattr_get, + .set = sdfat_xattr_set, +}; + +const struct xattr_handler *sdfat_xattr_handlers[] = { + &sdfat_xattr_handler, + NULL +}; + +void setup_sdfat_xattr_handler(struct super_block *sb) +{ + sb->s_xattr = sdfat_xattr_handlers; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) */ +int sdfat_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) +{ + return __sdfat_xattr_check_support(name); +} + +ssize_t sdfat_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) +{ + return __sdfat_getxattr(name, value, size); +} + +int sdfat_removexattr(struct dentry *dentry, const char *name) +{ + return __sdfat_xattr_check_support(name); +} + +void setup_sdfat_xattr_handler(struct super_block *sb) +{ + /* DO NOTHING */ +} +#endif diff --git a/fs/sync.c b/fs/sync.c index 1022b89727e5f..4e93b274a2c18 100644 --- a/fs/sync.c +++ b/fs/sync.c @@ -205,6 +205,7 @@ static int do_fsync(unsigned int fd, int datasync) if (f.file) { ret = vfs_fsync(f.file, datasync); fdput(f); + inc_syscfs(current); } return ret; } diff --git a/include/crypto/internal/hash.h b/include/crypto/internal/hash.h index f40acc953ed8a..313c76fbc5e1f 100644 --- a/include/crypto/internal/hash.h +++ b/include/crypto/internal/hash.h @@ -70,6 +70,14 @@ int ahash_register_instance(struct crypto_template *tmpl, struct ahash_instance *inst); void ahash_free_instance(struct crypto_instance *inst); +int shash_no_setkey(struct crypto_shash *tfm, const u8 *key, + unsigned int keylen); + +static inline bool crypto_shash_alg_has_setkey(struct shash_alg *alg) +{ + return alg->setkey != shash_no_setkey; +} + int crypto_init_ahash_spawn(struct crypto_ahash_spawn *spawn, struct hash_alg_common *alg, struct crypto_instance *inst); diff --git a/include/linux/compiler.h b/include/linux/compiler.h index 6977192bdb595..9df1978e064ce 100644 --- a/include/linux/compiler.h +++ b/include/linux/compiler.h @@ -179,6 +179,80 @@ void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect); # define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __LINE__) #endif +#include + +static __always_inline void data_access_exceeds_word_size(void) +#ifdef __compiletime_warning +__compiletime_warning("data access exceeds word size and won't be atomic") +#endif +; + +static __always_inline void data_access_exceeds_word_size(void) +{ +} + +static __always_inline void __read_once_size(volatile void *p, void *res, int size) +{ + switch (size) { + case 1: *(__u8 *)res = *(volatile __u8 *)p; break; + case 2: *(__u16 *)res = *(volatile __u16 *)p; break; + case 4: *(__u32 *)res = *(volatile __u32 *)p; break; +#ifdef CONFIG_64BIT + case 8: *(__u64 *)res = *(volatile __u64 *)p; break; +#endif + default: + barrier(); + __builtin_memcpy((void *)res, (const void *)p, size); + data_access_exceeds_word_size(); + barrier(); + } +} + +static __always_inline void __assign_once_size(volatile void *p, void *res, int size) +{ + switch (size) { + case 1: *(volatile __u8 *)p = *(__u8 *)res; break; + case 2: *(volatile __u16 *)p = *(__u16 *)res; break; + case 4: *(volatile __u32 *)p = *(__u32 *)res; break; +#ifdef CONFIG_64BIT + case 8: *(volatile __u64 *)p = *(__u64 *)res; break; +#endif + default: + barrier(); + __builtin_memcpy((void *)p, (const void *)res, size); + data_access_exceeds_word_size(); + barrier(); + } +} + +/* + * Prevent the compiler from merging or refetching reads or writes. The + * compiler is also forbidden from reordering successive instances of + * READ_ONCE, ASSIGN_ONCE and ACCESS_ONCE (see below), but only when the + * compiler is aware of some particular ordering. One way to make the + * compiler aware of ordering is to put the two invocations of READ_ONCE, + * ASSIGN_ONCE or ACCESS_ONCE() in different C statements. + * + * In contrast to ACCESS_ONCE these two macros will also work on aggregate + * data types like structs or unions. If the size of the accessed data + * type exceeds the word size of the machine (e.g., 32 bits or 64 bits) + * READ_ONCE() and ASSIGN_ONCE() will fall back to memcpy and print a + * compile-time warning. + * + * Their two major use cases are: (1) Mediating communication between + * process-level code and irq/NMI handlers, all running on the same CPU, + * and (2) Ensuring that the compiler does not fold, spindle, or otherwise + * mutilate accesses that either do not require ordering or that interact + * with an explicit memory barrier or atomic instruction that provides the + * required ordering. + */ + +#define READ_ONCE(x) \ + ({ typeof(x) __val; __read_once_size(&x, &__val, sizeof(__val)); __val; }) + +#define ASSIGN_ONCE(val, x) \ + ({ typeof(x) __val; __val = val; __assign_once_size(&x, &__val, sizeof(__val)); __val; }) + #endif /* __KERNEL__ */ #endif /* __ASSEMBLY__ */ diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h index 134e83d235da7..8f0406230a0a4 100644 --- a/include/linux/cpuidle.h +++ b/include/linux/cpuidle.h @@ -83,7 +83,6 @@ struct cpuidle_device { }; DECLARE_PER_CPU(struct cpuidle_device *, cpuidle_devices); -DECLARE_PER_CPU(struct cpuidle_device, cpuidle_dev); /** * cpuidle_get_last_residency - retrieves the last state's residency time @@ -112,9 +111,6 @@ struct cpuidle_driver { struct cpuidle_state states[CPUIDLE_STATE_MAX]; int state_count; int safe_state_index; - - /* the driver handles the cpus in cpumask */ - struct cpumask *cpumask; }; #ifdef CONFIG_CPU_IDLE @@ -139,6 +135,9 @@ extern void cpuidle_disable_device(struct cpuidle_device *dev); extern int cpuidle_play_dead(void); extern struct cpuidle_driver *cpuidle_get_cpu_driver(struct cpuidle_device *dev); +extern int cpuidle_register_cpu_driver(struct cpuidle_driver *drv, int cpu); +extern void cpuidle_unregister_cpu_driver(struct cpuidle_driver *drv, int cpu); + #else static inline void disable_cpuidle(void) { } static inline int cpuidle_idle_call(void) { return -ENODEV; } diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index 085197bd88120..6ed63824f82ae 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -79,8 +80,10 @@ static inline struct file * fcheck_files(struct files_struct *files, unsigned in struct file * file = NULL; struct fdtable *fdt = files_fdtable(files); - if (fd < fdt->max_fds) + if (fd < fdt->max_fds) { + fd = array_index_nospec(fd, fdt->max_fds); file = rcu_dereference_check_fdtable(files, fdt->fd[fd]); + } return file; } diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h index 478050b5c2a95..1fb740fad2429 100644 --- a/include/linux/fsnotify.h +++ b/include/linux/fsnotify.h @@ -237,11 +237,19 @@ static inline void fsnotify_open(struct file *file) { struct path *path = &file->f_path; struct inode *inode = path->dentry->d_inode; + struct path lower_path; + __u32 mask = FS_OPEN; if (S_ISDIR(inode->i_mode)) mask |= FS_ISDIR; + if (path->dentry->d_op && path->dentry->d_op->d_canonical_path) { + path->dentry->d_op->d_canonical_path(path, &lower_path); + fsnotify_parent(&lower_path, NULL, mask); + fsnotify(lower_path.dentry->d_inode, mask, &lower_path, FSNOTIFY_EVENT_PATH, NULL, 0); + path_put(&lower_path); + } fsnotify_parent(path, NULL, mask); fsnotify(inode, mask, path, FSNOTIFY_EVENT_PATH, NULL, 0); } diff --git a/include/linux/irqchip/msm-mpm-irq.h b/include/linux/irqchip/msm-mpm-irq.h index d59444330b42d..7b8a6a4765607 100644 --- a/include/linux/irqchip/msm-mpm-irq.h +++ b/include/linux/irqchip/msm-mpm-irq.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -98,7 +98,7 @@ bool msm_mpm_gpio_irqs_detectable(bool from_idle); * Low power management code calls into this API to configure the MPM to * monitor the active irqs before going to sleep. */ -void msm_mpm_enter_sleep(uint64_t sclk_count, bool from_idle, +void msm_mpm_enter_sleep(uint32_t sclk_count, bool from_idle, const struct cpumask *cpumask); /** * msm_mpm_exit_sleep() -Called from PM code after resuming from low power mode @@ -140,7 +140,7 @@ static inline bool msm_mpm_irqs_detectable(bool from_idle) { return false; } static inline bool msm_mpm_gpio_irqs_detectable(bool from_idle) { return false; } -static inline void msm_mpm_enter_sleep(uint64_t sclk_count, bool from_idle, +static inline void msm_mpm_enter_sleep(uint32_t sclk_count, bool from_idle, const struct cpumask *cpumask) {} static inline void msm_mpm_exit_sleep(bool from_idle) {} static inline void __init of_mpm_init(void) {} diff --git a/include/linux/ktime.h b/include/linux/ktime.h index e6aacdbebafda..638794b816ef2 100644 --- a/include/linux/ktime.h +++ b/include/linux/ktime.h @@ -71,13 +71,12 @@ typedef union ktime ktime_t; /* Kill this */ * * Return the ktime_t representation of the value */ -static inline ktime_t ktime_set(const long secs, const unsigned long nsecs) +static inline ktime_t ktime_set(const s64 secs, const unsigned long nsecs) { -#if (BITS_PER_LONG == 64) if (unlikely(secs >= KTIME_SEC_MAX)) return (ktime_t){ .tv64 = KTIME_MAX }; -#endif - return (ktime_t) { .tv64 = (s64)secs * NSEC_PER_SEC + (s64)nsecs }; + + return (ktime_t) { .tv64 = secs * NSEC_PER_SEC + (s64)nsecs }; } /* Subtract two ktime_t variables. rem = lhs -rhs: */ @@ -317,6 +316,30 @@ static inline u64 ktime_divns(const ktime_t kt, s64 div) # define ktime_divns(kt, div) (u64)((kt).tv64 / (div)) #endif +/** + * ktime_after - Compare if a ktime_t value is bigger than another one. + * @cmp1: comparable1 + * @cmp2: comparable2 + * + * Return: true if cmp1 happened after cmp2. + */ +static inline bool ktime_after(const ktime_t cmp1, const ktime_t cmp2) +{ + return ktime_compare(cmp1, cmp2) > 0; +} + +/** + * ktime_before - Compare if a ktime_t value is smaller than another one. + * @cmp1: comparable1 + * @cmp2: comparable2 + * + * Return: true if cmp1 happened before cmp2. + */ +static inline bool ktime_before(const ktime_t cmp1, const ktime_t cmp2) +{ + return ktime_compare(cmp1, cmp2) < 0; +} + static inline s64 ktime_to_us(const ktime_t kt) { return ktime_divns(kt, NSEC_PER_USEC); diff --git a/include/linux/mm.h b/include/linux/mm.h index e4bef3028af0a..ce8ec01d02673 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -70,6 +70,9 @@ extern unsigned long sysctl_admin_reserve_kbytes; /* to align the pointer to the (next) page boundary */ #define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE) +/* test whether an address (unsigned long or pointer) is aligned to PAGE_SIZE */ +#define PAGE_ALIGNED(addr) IS_ALIGNED((unsigned long)addr, PAGE_SIZE) + /* * Linux kernel virtual memory manager primitives. * The idea being to have a "virtual" mm in the same way diff --git a/include/linux/netlink.h b/include/linux/netlink.h index 9516dad451091..b3c6501ae3d2b 100644 --- a/include/linux/netlink.h +++ b/include/linux/netlink.h @@ -103,6 +103,7 @@ int netlink_sendskb(struct sock *sk, struct sk_buff *skb); struct netlink_callback { struct sk_buff *skb; const struct nlmsghdr *nlh; + int (*start)(struct netlink_callback *); int (*dump)(struct sk_buff * skb, struct netlink_callback *cb); int (*done)(struct netlink_callback *cb); @@ -125,6 +126,7 @@ struct nlmsghdr * __nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int len, int flags); struct netlink_dump_control { + int (*start)(struct netlink_callback *); int (*dump)(struct sk_buff *skb, struct netlink_callback *); int (*done)(struct netlink_callback *); void *data; diff --git a/include/linux/nospec.h b/include/linux/nospec.h new file mode 100644 index 0000000000000..e791ebc65c9c0 --- /dev/null +++ b/include/linux/nospec.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2018 Linus Torvalds. All rights reserved. +// Copyright(c) 2018 Alexei Starovoitov. All rights reserved. +// Copyright(c) 2018 Intel Corporation. All rights reserved. + +#ifndef _LINUX_NOSPEC_H +#define _LINUX_NOSPEC_H +#include + +/** + * array_index_mask_nospec() - generate a ~0 mask when index < size, 0 otherwise + * @index: array element index + * @size: number of elements in array + * + * When @index is out of bounds (@index >= @size), the sign bit will be + * set. Extend the sign bit to all bits and invert, giving a result of + * zero for an out of bounds index, or ~0 if within bounds [0, @size). + */ +#ifndef array_index_mask_nospec +static inline unsigned long array_index_mask_nospec(unsigned long index, + unsigned long size) +{ + /* + * Always calculate and emit the mask even if the compiler + * thinks the mask is not needed. The compiler does not take + * into account the value of @index under speculation. + */ + OPTIMIZER_HIDE_VAR(index); + return ~(long)(index | (size - 1UL - index)) >> (BITS_PER_LONG - 1); +} +#endif + +/* + * array_index_nospec - sanitize an array index after a bounds check + * + * For a code sequence like: + * + * if (index < size) { + * index = array_index_nospec(index, size); + * val = array[index]; + * } + * + * ...if the CPU speculates past the bounds check then + * array_index_nospec() will clamp the index within the range of [0, + * size). + */ +#define array_index_nospec(index, size) \ +({ \ + typeof(index) _i = (index); \ + typeof(size) _s = (size); \ + unsigned long _mask = array_index_mask_nospec(_i, _s); \ + \ + BUILD_BUG_ON(sizeof(_i) > sizeof(long)); \ + BUILD_BUG_ON(sizeof(_s) > sizeof(long)); \ + \ + (typeof(_i)) (_i & _mask); \ +}) +#endif /* _LINUX_NOSPEC_H */ diff --git a/include/linux/powersuspend.h b/include/linux/powersuspend.h deleted file mode 100644 index 5d187022fe89d..0000000000000 --- a/include/linux/powersuspend.h +++ /dev/null @@ -1,54 +0,0 @@ -/* include/linux/powersuspend.h - * - * Copyright (C) 2007-2008 Google, Inc. - * Copyright (C) 2013 Paul Reioux - * - * Modified by Jean-Pierre Rasquin - * - * v1.1 - make powersuspend not depend on a userspace initiator anymore, - * but use a hook in autosleep instead. - * - * v1.2 - make kernel / userspace mode switchable - * - * v1.3 - add a hook in display panel driver as alternative kernel trigger - * - * v1.4 - add a hybrid-kernel mode, accepting both kernel hooks (first wins) - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _LINUX_POWERSUSPEND_H -#define _LINUX_POWERSUSPEND_H - -#include - -#define POWER_SUSPEND_INACTIVE 0 -#define POWER_SUSPEND_ACTIVE 1 - -#define POWER_SUSPEND_AUTOSLEEP 0 // Use kernel autosleep as hook -#define POWER_SUSPEND_USERSPACE 1 // Use fauxclock as trigger -#define POWER_SUSPEND_PANEL 2 // Use display panel state as hook -#define POWER_SUSPEND_HYBRID 3 // Use display panel state and autosleep as hook - -struct power_suspend { - struct list_head link; - void (*suspend)(struct power_suspend *h); - void (*resume)(struct power_suspend *h); -}; - -void register_power_suspend(struct power_suspend *handler); -void unregister_power_suspend(struct power_suspend *handler); - -void set_power_suspend_state_autosleep_hook(int new_state); -void set_power_suspend_state_panel_hook(int new_state); - -#endif - diff --git a/include/linux/qpnp/pwm.h b/include/linux/qpnp/pwm.h index 50fb2e52a225e..3d8de231545ec 100644 --- a/include/linux/qpnp/pwm.h +++ b/include/linux/qpnp/pwm.h @@ -152,6 +152,11 @@ int pwm_lut_config(struct pwm_device *pwm, int period_us, int pwm_config_us(struct pwm_device *pwm, int duty_us, int period_us); +/* + * synchronized enable of multiple pwm instances + */ +int pwm_enable_synchronized(struct pwm_device **pwms, size_t num); + /* Standard APIs supported */ /* * pwm_request - request a PWM device diff --git a/include/linux/quickwakeup.h b/include/linux/quickwakeup.h deleted file mode 100644 index 000effa00763c..0000000000000 --- a/include/linux/quickwakeup.h +++ /dev/null @@ -1,46 +0,0 @@ -/* include/linux/quickwakeup.h - * - * Copyright (C) 2014 Motorola Mobility LLC. - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _QUICKWAKEUP_H_ -#define _QUICKWAKEUP_H_ - -#ifdef __KERNEL__ - -struct quickwakeup_ops { - struct list_head list; - char *name; - int (*qw_execute)(void *data); - int (*qw_check)(void *data); - int execute; - void *data; /* arbitrary data passed back to user */ -}; - -#ifdef CONFIG_QUICK_WAKEUP - -int quickwakeup_register(struct quickwakeup_ops *ops); -void quickwakeup_unregister(struct quickwakeup_ops *ops); -bool quickwakeup_suspend_again(void); - -#else - -static inline int quickwakeup_register(struct quickwakeup_ops *ops) { return 0; }; -static inline void quickwakeup_unregister(struct quickwakeup_ops *ops) {}; -static inline bool quickwakeup_suspend_again(void) { return 0; }; - -#endif /* CONFIG_QUICK_WAKEUP */ - -#endif /* __KERNEL__ */ - -#endif /* _QUICKWAKEUP_H_ */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 3bf2401492365..aeb29890c8d57 100755 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -3231,6 +3231,11 @@ static inline void inc_syscw(struct task_struct *tsk) { tsk->ioac.syscw++; } + +static inline void inc_syscfs(struct task_struct *tsk) +{ + tsk->ioac.syscfs++; +} #else static inline void add_rchar(struct task_struct *tsk, ssize_t amt) { @@ -3247,6 +3252,9 @@ static inline void inc_syscr(struct task_struct *tsk) static inline void inc_syscw(struct task_struct *tsk) { } +static inline void inc_syscfs(struct task_struct *tsk) +{ +} #endif #ifndef TASK_SIZE_OF diff --git a/include/linux/task_io_accounting.h b/include/linux/task_io_accounting.h index bdf855c2856fb..2dd338fdf8815 100644 --- a/include/linux/task_io_accounting.h +++ b/include/linux/task_io_accounting.h @@ -18,6 +18,8 @@ struct task_io_accounting { u64 syscr; /* # of write syscalls */ u64 syscw; + /* # of fsync syscalls */ + u64 syscfs; #endif /* CONFIG_TASK_XACCT */ #ifdef CONFIG_TASK_IO_ACCOUNTING diff --git a/include/linux/task_io_accounting_ops.h b/include/linux/task_io_accounting_ops.h index 4d090f9ee6087..1b505c804af34 100644 --- a/include/linux/task_io_accounting_ops.h +++ b/include/linux/task_io_accounting_ops.h @@ -96,6 +96,7 @@ static inline void task_chr_io_accounting_add(struct task_io_accounting *dst, dst->wchar += src->wchar; dst->syscr += src->syscr; dst->syscw += src->syscw; + dst->syscfs += src->syscfs; } #else static inline void task_chr_io_accounting_add(struct task_io_accounting *dst, diff --git a/include/net/genetlink.h b/include/net/genetlink.h index 8e0b6c856a130..d0c5bb84a257c 100644 --- a/include/net/genetlink.h +++ b/include/net/genetlink.h @@ -105,6 +105,7 @@ static inline void genl_info_net_set(struct genl_info *info, struct net *net) * @flags: flags * @policy: attribute validation policy * @doit: standard command callback + * @start: start callback for dumps * @dumpit: callback for dumpers * @done: completion callback for dumps * @ops_list: operations list @@ -116,6 +117,7 @@ struct genl_ops { const struct nla_policy *policy; int (*doit)(struct sk_buff *skb, struct genl_info *info); + int (*start)(struct netlink_callback *cb); int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb); int (*done)(struct netlink_callback *cb); diff --git a/include/soc/qcom/pm.h b/include/soc/qcom/pm.h index 4801275c28ba8..076ab9c39b1f7 100644 --- a/include/soc/qcom/pm.h +++ b/include/soc/qcom/pm.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved. * Author: San Mehat * * This software is licensed under the terms of the GNU General Public @@ -45,7 +45,6 @@ enum msm_pm_l2_scm_flag { MSM_SCM_L2_OFF = 1, MSM_SCM_L2_RET = 2, MSM_SCM_L2_GDHS = 3, - MSM_SCM_L3_PC_OFF = 4, }; #define MSM_PM_MODE(cpu, mode_nr) ((cpu) * MSM_PM_SLEEP_MODE_NR + (mode_nr)) diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h index c86e81d04375d..85486567cb205 100644 --- a/include/soc/qcom/spm.h +++ b/include/soc/qcom/spm.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -25,9 +25,6 @@ enum { struct msm_spm_device; struct device_node; -#define spm_raw_write(v, a) ({ __raw_writel(v, a); __raw_readl(a); }) -#define spm_write_relaxed(v, c) ({ writel_relaxed(v, c); readl_relaxed(c); }) - #if defined(CONFIG_MSM_SPM) int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); @@ -39,8 +36,6 @@ int msm_spm_turn_on_cpu_rail(struct device_node *l2ccc_node, struct msm_spm_device *msm_spm_get_device_by_name(const char *name); int msm_spm_config_low_power_mode(struct msm_spm_device *dev, unsigned int mode, bool notify_rpm); -int msm_spm_config_low_power_mode_addr(struct msm_spm_device *dev, - unsigned int mode, bool notify_rpm); int msm_spm_device_init(void); bool msm_spm_is_mode_avail(unsigned int mode); void msm_spm_dump_regs(unsigned int cpu); @@ -100,25 +95,17 @@ static inline void msm_spm_dump_regs(unsigned int cpu) return; } -static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev, +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, unsigned int mode, bool notify_rpm) { return -ENODEV; } -static inline int msm_spm_config_low_power_mode_addr( - struct msm_spm_device *dev, unsigned int mode, bool notify_rpm) -{ - return -ENODEV; -} - - -static inline struct msm_spm_device *msm_spm_get_device_by_name - (const char *name) +struct msm_spm_device *msm_spm_get_device_by_name(const char *name) { return NULL; } -static inline bool msm_spm_is_mode_avail(unsigned int mode) +bool msm_spm_is_mode_avail(unsigned int mode) { return false; } diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 42d830afcf672..4c0e95b47213f 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -397,6 +397,7 @@ struct snd_pcm_substream { struct snd_pcm_ops *ops; /* -- runtime information -- */ struct snd_pcm_runtime *runtime; + spinlock_t runtime_lock; /* -- timer section -- */ struct snd_timer *timer; /* timer */ unsigned timer_running: 1; /* time is running */ diff --git a/include/trace/events/trace_msm_low_power.h b/include/trace/events/trace_msm_low_power.h index b06185fd57785..2d56c89870621 100644 --- a/include/trace/events/trace_msm_low_power.h +++ b/include/trace/events/trace_msm_low_power.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012, 2014-2015 The Linux Foundation. All rights reserved. +/* Copyright (c) 2012, 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -18,31 +18,6 @@ #include -TRACE_EVENT(cpu_power_select, - - TP_PROTO(int index, u32 sleep_us, u32 latency, u32 next_event_us), - - TP_ARGS(index, sleep_us, latency, next_event_us), - - TP_STRUCT__entry( - __field(int, index) - __field(u32, sleep_us) - __field(u32, latency) - __field(u32, next_event_us) - ), - - TP_fast_assign( - __entry->index = index; - __entry->sleep_us = sleep_us; - __entry->latency = latency; - __entry->next_event_us = next_event_us; - ), - - TP_printk("idx:%d sleep_time:%u latency:%u next_event:%u", - __entry->index, __entry->sleep_us, __entry->latency, - __entry->next_event_us) -); - TRACE_EVENT(cpu_idle_enter, TP_PROTO(int index), @@ -60,7 +35,6 @@ TRACE_EVENT(cpu_idle_enter, TP_printk("idx:%d", __entry->index) ); - TRACE_EVENT(cpu_idle_exit, TP_PROTO(int index, bool success), diff --git a/include/uapi/linux/qseecom.h b/include/uapi/linux/qseecom.h index aac13eef45830..fae2e05552280 100644 --- a/include/uapi/linux/qseecom.h +++ b/include/uapi/linux/qseecom.h @@ -107,6 +107,13 @@ struct qseecom_qseos_version_req { unsigned int qseos_version; /* in */ }; +/* + * struct qseecom_qsee_version_req - get qsee version + * @qsee_version - version number + */ +struct qseecom_qsee_version_req { + unsigned int qsee_version; +}; /* * struct qseecom_qseos_app_load_query - verify if app is loaded in qsee * @app_name[MAX_APP_NAME_SIZE]- name of the app. @@ -293,4 +300,6 @@ extern long qseecom_ioctl(struct file *file, #define QSEECOM_QTEEC_IOCTL_REQUEST_CANCELLATION_REQ \ _IOWR(QSEECOM_IOC_MAGIC, 33, struct qseecom_qteec_modfd_req) +#define QSEECOM_IOCTL_GET_QSEE_VERSION_REQ \ + _IOWR(QSEECOM_IOC_MAGIC, 37, struct qseecom_qsee_version_req) #endif /* _UAPI_QSEECOM_H_ */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index cedad8eb14755..4f944f5ba66ee 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -739,7 +739,6 @@ enum v4l2_mpeg_vidc_extradata { V4L2_MPEG_VIDC_EXTRADATA_FRAME_BITS_INFO = 19, V4L2_MPEG_VIDC_EXTRADATA_LTR = 20, V4L2_MPEG_VIDC_EXTRADATA_METADATA_MBI = 21, - V4L2_MPEG_VIDC_EXTRADATA_VUI_DISPLAY = 22, }; #define V4L2_CID_MPEG_VIDC_SET_PERF_LEVEL (V4L2_CID_MPEG_MSM_VIDC_BASE + 26) diff --git a/include/uapi/media/msm_vidc.h b/include/uapi/media/msm_vidc.h index dc2fa5724f9b6..6326030eb6e64 100644 --- a/include/uapi/media/msm_vidc.h +++ b/include/uapi/media/msm_vidc.h @@ -109,23 +109,6 @@ struct msm_vidc_s3d_frame_packing_payload { unsigned int fpa_extension_flag; }; -struct msm_vidc_vui_display_info_payload { - unsigned int video_signal_present_flag; - unsigned int video_format; - unsigned int bit_depth_y; - unsigned int bit_depth_c; - unsigned int video_full_range_flag; - unsigned int color_description_present_flag; - unsigned int color_primaries; - unsigned int transfer_characteristics; - unsigned int matrix_coefficients; - unsigned int chroma_location_info_present_flag; - unsigned int chroma_format_idc; - unsigned int separate_color_plane_flag; - unsigned int chroma_sample_loc_type_top_field; - unsigned int chroma_sample_loc_type_bottom_field; -}; - enum msm_vidc_extradata_type { MSM_VIDC_EXTRADATA_NONE = 0x00000000, MSM_VIDC_EXTRADATA_MB_QUANTIZATION = 0x00000001, @@ -150,7 +133,6 @@ enum msm_vidc_extradata_type { MSM_VIDC_EXTRADATA_METADATA_LTR = 0x7F100004, MSM_VIDC_EXTRADATA_METADATA_FILLER = 0x7FE00002, MSM_VIDC_EXTRADATA_METADATA_MBI = 0x7F100005, - MSM_VIDC_EXTRADATA_VUI_DISPLAY_INFO = 0x7F100006, }; enum msm_vidc_interlace_type { MSM_VIDC_INTERLACE_FRAME_PROGRESSIVE = 0x01, diff --git a/kernel/events/hw_breakpoint.c b/kernel/events/hw_breakpoint.c index 20185ea64aa6f..390846433239a 100644 --- a/kernel/events/hw_breakpoint.c +++ b/kernel/events/hw_breakpoint.c @@ -445,16 +445,9 @@ EXPORT_SYMBOL_GPL(register_user_hw_breakpoint); * modify_user_hw_breakpoint - modify a user-space hardware breakpoint * @bp: the breakpoint structure to modify * @attr: new breakpoint attributes - * @triggered: callback to trigger when we hit the breakpoint - * @tsk: pointer to 'task_struct' of the process to which the address belongs */ int modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr) { - u64 old_addr = bp->attr.bp_addr; - u64 old_len = bp->attr.bp_len; - int old_type = bp->attr.bp_type; - int err = 0; - /* * modify_user_hw_breakpoint can be invoked with IRQs disabled and hence it * will not be possible to raise IPIs that invoke __perf_event_disable. @@ -469,27 +462,18 @@ int modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *att bp->attr.bp_addr = attr->bp_addr; bp->attr.bp_type = attr->bp_type; bp->attr.bp_len = attr->bp_len; + bp->attr.disabled = 1; - if (attr->disabled) - goto end; - - err = validate_hw_breakpoint(bp); - if (!err) - perf_event_enable(bp); + if (!attr->disabled) { + int err = validate_hw_breakpoint(bp); - if (err) { - bp->attr.bp_addr = old_addr; - bp->attr.bp_type = old_type; - bp->attr.bp_len = old_len; - if (!bp->attr.disabled) - perf_event_enable(bp); + if (err) + return err; - return err; + perf_event_enable(bp); + bp->attr.disabled = 0; } -end: - bp->attr.disabled = attr->disabled; - return 0; } EXPORT_SYMBOL_GPL(modify_user_hw_breakpoint); diff --git a/kernel/futex.c b/kernel/futex.c index f676b873d07d1..800994c0b388f 100644 --- a/kernel/futex.c +++ b/kernel/futex.c @@ -243,6 +243,7 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, int rw) unsigned long address = (unsigned long)uaddr; struct mm_struct *mm = current->mm; struct page *page, *page_head; + struct address_space *mapping; int err, ro = 0; /* @@ -253,6 +254,9 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, int rw) return -EINVAL; address -= key->both.offset; + if (unlikely(!access_ok(rw, uaddr, sizeof(u32)))) + return -EFAULT; + /* * PROCESS_PRIVATE futexes are fast. * As the mm cannot disappear under us and the 'key' only needs @@ -261,8 +265,6 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, int rw) * but access_ok() should be faster than find_vma() */ if (!fshared) { - if (unlikely(!access_ok(VERIFY_WRITE, uaddr, sizeof(u32)))) - return -EFAULT; key->private.mm = mm; key->private.address = address; get_futex_key_refs(key); @@ -320,7 +322,19 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, int rw) } #endif - lock_page(page_head); + /* + * The treatment of mapping from this point on is critical. The page + * lock protects many things but in this context the page lock + * stabilizes mapping, prevents inode freeing in the shared + * file-backed region case and guards against movement to swap cache. + * + * Strictly speaking the page lock is not needed in all cases being + * considered here and page lock forces unnecessarily serialization + * From this point on, mapping will be re-verified if necessary and + * page lock will be acquired only if it is unavoidable + */ + + mapping = READ_ONCE(page_head->mapping); /* * If page_head->mapping is NULL, then it cannot be a PageAnon @@ -337,18 +351,31 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, int rw) * shmem_writepage move it from filecache to swapcache beneath us: * an unlikely race, but we do need to retry for page_head->mapping. */ - if (!page_head->mapping) { - int shmem_swizzled = PageSwapCache(page_head); + if (unlikely(!mapping)) { + int shmem_swizzled; + + /* + * Page lock is required to identify which special case above + * applies. If this is really a shmem page then the page lock + * will prevent unexpected transitions. + */ + lock_page(page); + shmem_swizzled = PageSwapCache(page) || page->mapping; unlock_page(page_head); put_page(page_head); + if (shmem_swizzled) goto again; + return -EFAULT; } /* * Private mappings are handled in a simple way. * + * If the futex key is stored on an anonymous page, then the associated + * object is the mm which is implicitly pinned by the calling process. + * * NOTE: When userspace waits on a MAP_SHARED mapping, even if * it's a read-only handle, it's expected that futexes attach to * the object not the particular process. @@ -366,16 +393,74 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, int rw) key->both.offset |= FUT_OFF_MMSHARED; /* ref taken on mm */ key->private.mm = mm; key->private.address = address; + + get_futex_key_refs(key); /* implies smp_mb(); (B) */ + } else { + struct inode *inode; + + /* + * The associated futex object in this case is the inode and + * the page->mapping must be traversed. Ordinarily this should + * be stabilised under page lock but it's not strictly + * necessary in this case as we just want to pin the inode, not + * update the radix tree or anything like that. + * + * The RCU read lock is taken as the inode is finally freed + * under RCU. If the mapping still matches expectations then the + * mapping->host can be safely accessed as being a valid inode. + */ + rcu_read_lock(); + + if (READ_ONCE(page_head->mapping) != mapping) { + rcu_read_unlock(); + put_page(page_head); + + goto again; + } + + inode = READ_ONCE(mapping->host); + if (!inode) { + rcu_read_unlock(); + put_page(page_head); + + goto again; + } + + /* + * Take a reference unless it is about to be freed. Previously + * this reference was taken by ihold under the page lock + * pinning the inode in place so i_lock was unnecessary. The + * only way for this check to fail is if the inode was + * truncated in parallel so warn for now if this happens. + * + * We are not calling into get_futex_key_refs() in file-backed + * cases, therefore a successful atomic_inc return below will + * guarantee that get_futex_key() will still imply smp_mb(); (B). + */ + if (WARN_ON_ONCE(!atomic_inc_not_zero(&inode->i_count))) { + rcu_read_unlock(); + put_page(page_head); + + goto again; + } + + /* Should be impossible but lets be paranoid for now */ + if (WARN_ON_ONCE(inode->i_mapping != mapping)) { + err = -EFAULT; + rcu_read_unlock(); + iput(inode); + + goto out; + } + key->both.offset |= FUT_OFF_INODE; /* inode-based key */ - key->shared.inode = page_head->mapping->host; + key->shared.inode = inode; key->shared.pgoff = basepage_index(page); + rcu_read_unlock(); } - get_futex_key_refs(key); - out: - unlock_page(page_head); put_page(page_head); return err; } diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index bbd1dee81fdd8..116b2276032cc 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -480,6 +480,7 @@ void destroy_hrtimer_on_stack(struct hrtimer *timer) { debug_object_free(timer, &hrtimer_debug_descr); } +EXPORT_SYMBOL_GPL(destroy_hrtimer_on_stack); #else static inline void debug_hrtimer_init(struct hrtimer *timer) { } diff --git a/kernel/posix-timers.c b/kernel/posix-timers.c index 77e6b83c04315..ea6f6a49eeca5 100644 --- a/kernel/posix-timers.c +++ b/kernel/posix-timers.c @@ -48,6 +48,7 @@ #include #include #include +#include /* * Management arrays for POSIX timers. Timers are now kept in static hash table @@ -573,13 +574,21 @@ static void release_posix_timer(struct k_itimer *tmr, int it_id_set) static struct k_clock *clockid_to_kclock(const clockid_t id) { - if (id < 0) + struct k_clock *kc; + clockid_t idx = id; + + if (id < 0) { return (id & CLOCKFD_MASK) == CLOCKFD ? &clock_posix_dynamic : &clock_posix_cpu; + } + + if (id >= ARRAY_SIZE(posix_clocks)) + return NULL; - if (id >= MAX_CLOCKS || !posix_clocks[id].clock_getres) + kc = &posix_clocks[array_index_nospec(idx, ARRAY_SIZE(posix_clocks))]; + if (!kc->clock_getres) return NULL; - return &posix_clocks[id]; + return kc; } static int common_timer_create(struct k_itimer *new_timer) diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index ac8d61a3a2819..47d945fc4b630 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -26,13 +26,6 @@ config WAKELOCK bool default y -config POWERSUSPEND - bool "Power suspend" - default y - ---help--- - Call early suspend handlers when the user requested sleep state - changes. - config HIBERNATE_CALLBACKS bool @@ -320,11 +313,3 @@ config PM_SYNC_BEFORE_SUSPEND bool "Sync file systems before suspend" depends on SUSPEND default y - -config QUICK_WAKEUP - bool "Quick wakeup" - depends on SUSPEND - default y - ---help--- - Allow kernel driver to do periodic jobs without resuming the full system - This option can increase battery life on android powered smartphone. diff --git a/kernel/power/Makefile b/kernel/power/Makefile index fb7736b57b01e..74c713ba61b07 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -9,12 +9,10 @@ obj-$(CONFIG_SUSPEND) += suspend.o obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \ block_io.o -obj-$(CONFIG_POWERSUSPEND) += powersuspend.o obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o obj-$(CONFIG_SUSPEND_TIME) += suspend_time.o -obj-$(CONFIG_QUICK_WAKEUP) += quickwakeup.o obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o obj-$(CONFIG_SUSPEND) += wakeup_reason.o diff --git a/kernel/power/autosleep.c b/kernel/power/autosleep.c index 351ceb5244300..9012ecf7b814f 100644 --- a/kernel/power/autosleep.c +++ b/kernel/power/autosleep.c @@ -10,10 +10,6 @@ #include #include -#ifdef CONFIG_POWERSUSPEND -#include -#endif - #include "power.h" static suspend_state_t autosleep_state; @@ -109,16 +105,8 @@ int pm_autosleep_set_state(suspend_state_t state) if (state > PM_SUSPEND_ON) { pm_wakep_autosleep_enabled(true); queue_up_suspend_work(); -#ifdef CONFIG_POWERSUSPEND - // Yank555.lu : add hook to handle powersuspend tasks (sleep) - set_power_suspend_state_autosleep_hook(POWER_SUSPEND_ACTIVE); -#endif } else { pm_wakep_autosleep_enabled(false); -#ifdef CONFIG_POWERSUSPEND - // Yank555.lu : add hook to handle powersuspend tasks (wakeup) - set_power_suspend_state_autosleep_hook(POWER_SUSPEND_INACTIVE); -#endif } mutex_unlock(&autosleep_lock); diff --git a/kernel/power/powersuspend.c b/kernel/power/powersuspend.c deleted file mode 100644 index 2b7fe5059dbbb..0000000000000 --- a/kernel/power/powersuspend.c +++ /dev/null @@ -1,324 +0,0 @@ -/* kernel/power/powersuspend.c - * - * Copyright (C) 2005-2008 Google, Inc. - * Copyright (C) 2013 Paul Reioux - * - * Modified by Jean-Pierre Rasquin - * - * v1.1 - make powersuspend not depend on a userspace initiator anymore, - * but use a hook in autosleep instead. - * - * v1.2 - make kernel / userspace mode switchable - * - * v1.3 - add a hook in display panel driver as alternative kernel trigger - * - * v1.4 - add a hybrid-kernel mode, accepting both kernel hooks (first wins) - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include - -#define MAJOR_VERSION 1 -#define MINOR_VERSION 5 - -//#define POWER_SUSPEND_DEBUG // Add debugging prints in dmesg - -struct workqueue_struct *suspend_work_queue; - -static DEFINE_MUTEX(power_suspend_lock); -static LIST_HEAD(power_suspend_handlers); -static void power_suspend(struct work_struct *work); -static void power_resume(struct work_struct *work); -static DECLARE_WORK(power_suspend_work, power_suspend); -static DECLARE_WORK(power_resume_work, power_resume); -static DEFINE_SPINLOCK(state_lock); - -static int state; // Yank555.lu : Current powersave state (screen on / off) -static int mode; // Yank555.lu : Current powersave mode (kernel / userspace / panel / hybrid) - -void register_power_suspend(struct power_suspend *handler) -{ - struct list_head *pos; - - mutex_lock(&power_suspend_lock); - list_for_each(pos, &power_suspend_handlers) { - struct power_suspend *p; - p = list_entry(pos, struct power_suspend, link); - } - list_add_tail(&handler->link, pos); - mutex_unlock(&power_suspend_lock); -} -EXPORT_SYMBOL(register_power_suspend); - -void unregister_power_suspend(struct power_suspend *handler) -{ - mutex_lock(&power_suspend_lock); - list_del(&handler->link); - mutex_unlock(&power_suspend_lock); -} -EXPORT_SYMBOL(unregister_power_suspend); - -static void power_suspend(struct work_struct *work) -{ - struct power_suspend *pos; - unsigned long irqflags; - int abort = 0; - - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] entering suspend...\n"); - #endif - mutex_lock(&power_suspend_lock); - spin_lock_irqsave(&state_lock, irqflags); - if (state == POWER_SUSPEND_INACTIVE) - abort = 1; - spin_unlock_irqrestore(&state_lock, irqflags); - - if (abort) - goto abort_suspend; - - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] suspending...\n"); - #endif - list_for_each_entry(pos, &power_suspend_handlers, link) { - if (pos->suspend != NULL) { - pos->suspend(pos); - } - } - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] suspend completed.\n"); - #endif -abort_suspend: - mutex_unlock(&power_suspend_lock); -} - -static void power_resume(struct work_struct *work) -{ - struct power_suspend *pos; - unsigned long irqflags; - int abort = 0; - - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] entering resume...\n"); - #endif - mutex_lock(&power_suspend_lock); - spin_lock_irqsave(&state_lock, irqflags); - if (state == POWER_SUSPEND_ACTIVE) - abort = 1; - spin_unlock_irqrestore(&state_lock, irqflags); - - if (abort) - goto abort_resume; - - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] resuming...\n"); - #endif - list_for_each_entry_reverse(pos, &power_suspend_handlers, link) { - if (pos->resume != NULL) { - pos->resume(pos); - } - } - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] resume completed.\n"); - #endif -abort_resume: - mutex_unlock(&power_suspend_lock); -} - -void set_power_suspend_state(int new_state) -{ - unsigned long irqflags; - - spin_lock_irqsave(&state_lock, irqflags); - if (state == POWER_SUSPEND_INACTIVE && new_state == POWER_SUSPEND_ACTIVE) { - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] state activated.\n"); - #endif - state = new_state; - queue_work(suspend_work_queue, &power_suspend_work); - } else if (state == POWER_SUSPEND_ACTIVE && new_state == POWER_SUSPEND_INACTIVE) { - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] state deactivated.\n"); - #endif - state = new_state; - queue_work(suspend_work_queue, &power_resume_work); - } - spin_unlock_irqrestore(&state_lock, irqflags); -} - -void set_power_suspend_state_autosleep_hook(int new_state) -{ - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] autosleep resquests %s.\n", new_state == POWER_SUSPEND_ACTIVE ? "sleep" : "wakeup"); - #endif - // Yank555.lu : Only allow autosleep hook changes in autosleep & hybrid mode - if (mode == POWER_SUSPEND_AUTOSLEEP || mode == POWER_SUSPEND_HYBRID) - set_power_suspend_state(new_state); -} - -EXPORT_SYMBOL(set_power_suspend_state_autosleep_hook); - -void set_power_suspend_state_panel_hook(int new_state) -{ - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] panel resquests %s.\n", new_state == POWER_SUSPEND_ACTIVE ? "sleep" : "wakeup"); - #endif - // Yank555.lu : Only allow autosleep hook changes in autosleep & hybrid mode - if (mode == POWER_SUSPEND_PANEL || mode == POWER_SUSPEND_HYBRID) - set_power_suspend_state(new_state); -} - -EXPORT_SYMBOL(set_power_suspend_state_panel_hook); - -// ------------------------------------------ sysfs interface ------------------------------------------ - -static ssize_t power_suspend_state_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - return sprintf(buf, "%u\n", state); -} - -static ssize_t power_suspend_state_store(struct kobject *kobj, - struct kobj_attribute *attr, const char *buf, size_t count) -{ - int new_state = 0; - - // Yank555.lu : Only allow sysfs changes from userspace mode - if (mode != POWER_SUSPEND_USERSPACE) - return -EINVAL; - - sscanf(buf, "%d\n", &new_state); - - #ifdef POWER_SUSPEND_DEBUG - pr_info("[POWERSUSPEND] userspace resquests %s.\n", new_state == POWER_SUSPEND_ACTIVE ? "sleep" : "wakeup"); - #endif - if(new_state == POWER_SUSPEND_ACTIVE || new_state == POWER_SUSPEND_INACTIVE) - set_power_suspend_state(new_state); - - return count; -} - -static struct kobj_attribute power_suspend_state_attribute = - __ATTR(power_suspend_state, 0666, - power_suspend_state_show, - power_suspend_state_store); - -static ssize_t power_suspend_mode_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - return sprintf(buf, "%u\n", mode); -} - -static ssize_t power_suspend_mode_store(struct kobject *kobj, - struct kobj_attribute *attr, const char *buf, size_t count) -{ - int data = 0; - - sscanf(buf, "%d\n", &data); - - switch (data) { - case POWER_SUSPEND_AUTOSLEEP: - case POWER_SUSPEND_PANEL: - case POWER_SUSPEND_USERSPACE: - case POWER_SUSPEND_HYBRID: mode = data; - return count; - default: - return -EINVAL; - } - -} - -static struct kobj_attribute power_suspend_mode_attribute = - __ATTR(power_suspend_mode, 0666, - power_suspend_mode_show, - power_suspend_mode_store); - -static ssize_t power_suspend_version_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - return sprintf(buf, "version: %d.%d\n", MAJOR_VERSION, MINOR_VERSION); -} - -static struct kobj_attribute power_suspend_version_attribute = - __ATTR(power_suspend_version, 0444, - power_suspend_version_show, - NULL); - -static struct attribute *power_suspend_attrs[] = -{ - &power_suspend_state_attribute.attr, - &power_suspend_mode_attribute.attr, - &power_suspend_version_attribute.attr, - NULL, -}; - -static struct attribute_group power_suspend_attr_group = -{ - .attrs = power_suspend_attrs, -}; - -static struct kobject *power_suspend_kobj; - -// ------------------ sysfs interface ----------------------- -static int __init power_suspend_init(void) -{ - - int sysfs_result; - - power_suspend_kobj = kobject_create_and_add("power_suspend", - kernel_kobj); - if (!power_suspend_kobj) { - pr_err("%s kobject create failed!\n", __FUNCTION__); - return -ENOMEM; - } - - sysfs_result = sysfs_create_group(power_suspend_kobj, - &power_suspend_attr_group); - - if (sysfs_result) { - pr_info("%s group create failed!\n", __FUNCTION__); - kobject_put(power_suspend_kobj); - return -ENOMEM; - } - - suspend_work_queue = create_singlethread_workqueue("p-suspend"); - - if (suspend_work_queue == NULL) { - return -ENOMEM; - } - -// mode = POWER_SUSPEND_AUTOSLEEP; // Yank555.lu : Default to autosleep mode -// mode = POWER_SUSPEND_USERSPACE; // Yank555.lu : Default to userspace mode -// mode = POWER_SUSPEND_PANEL; // Yank555.lu : Default to display panel mode - mode = POWER_SUSPEND_HYBRID; // Yank555.lu : Default to display panel / autosleep hybrid mode - - return 0; -} - -static void __exit power_suspend_exit(void) -{ - if (power_suspend_kobj != NULL) - kobject_put(power_suspend_kobj); - - destroy_workqueue(suspend_work_queue); -} - -core_initcall(power_suspend_init); -module_exit(power_suspend_exit); - -MODULE_AUTHOR("Paul Reioux / Jean-Pierre Rasquin "); -MODULE_DESCRIPTION("power_suspend - A replacement kernel PM driver for" - "Android's deprecated early_suspend/late_resume PM driver!"); -MODULE_LICENSE("GPL v2"); - diff --git a/kernel/power/quickwakeup.c b/kernel/power/quickwakeup.c deleted file mode 100644 index 46f9dda7a3cb9..0000000000000 --- a/kernel/power/quickwakeup.c +++ /dev/null @@ -1,104 +0,0 @@ -/* kernel/power/quickwakeup.c - * - * Copyright (C) 2014 Motorola Mobility LLC. - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include - -static LIST_HEAD(qw_head); -static DEFINE_MUTEX(list_lock); - -int quickwakeup_register(struct quickwakeup_ops *ops) -{ - mutex_lock(&list_lock); - list_add(&ops->list, &qw_head); - mutex_unlock(&list_lock); - - return 0; -} - -void quickwakeup_unregister(struct quickwakeup_ops *ops) -{ - mutex_lock(&list_lock); - list_del(&ops->list); - mutex_unlock(&list_lock); -} - -static int quickwakeup_check(void) -{ - int check = 0; - struct quickwakeup_ops *index; - - mutex_lock(&list_lock); - - list_for_each_entry(index, &qw_head, list) { - int ret = index->qw_check(index->data); - index->execute = ret; - check |= ret; - pr_debug("%s: %s votes for %s\n", __func__, index->name, - ret ? "execute" : "dont care"); - } - - mutex_unlock(&list_lock); - - return check; -} - -/* return 1 => suspend again - return 0 => continue wakeup - */ -static int quickwakeup_execute(void) -{ - int suspend_again = 0; - int final_vote = 1; - struct quickwakeup_ops *index; - - mutex_lock(&list_lock); - - list_for_each_entry(index, &qw_head, list) { - if (index->execute) { - int ret = index->qw_execute(index->data); - index->execute = 0; - final_vote &= ret; - suspend_again = final_vote; - pr_debug("%s: %s votes for %s\n", __func__, index->name, - ret ? "suspend again" : "wakeup"); - } - } - - mutex_unlock(&list_lock); - - pr_debug("%s: %s\n", __func__, - suspend_again ? "suspend again" : "wakeup"); - - return suspend_again; -} - -/* return 1 => suspend again - return 0 => continue wakeup - */ -bool quickwakeup_suspend_again(void) -{ - int ret = 0; - - if (quickwakeup_check()) - ret = quickwakeup_execute(); - - pr_debug("%s- returning %d %s\n", __func__, ret, - ret ? "suspend again" : "wakeup"); - - return ret; -} diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 14f700398ba05..6c761ac119790 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -425,6 +425,16 @@ static void hidp_del_timer(struct hidp_session *session) del_timer(&session->timer); } +static void hidp_process_report(struct hidp_session *session, int type, + const u8 *data, unsigned int len, int intr) +{ + if (len > HID_MAX_BUFFER_SIZE) + len = HID_MAX_BUFFER_SIZE; + + memcpy(session->input_buf, data, len); + hid_input_report(session->hid, type, session->input_buf, len, intr); +} + static void hidp_process_handshake(struct hidp_session *session, unsigned char param) { @@ -498,7 +508,8 @@ static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb, hidp_input_report(session, skb); if (session->hid) - hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0); + hidp_process_report(session, HID_INPUT_REPORT, + skb->data, skb->len, 0); break; case HIDP_DATA_RTYPE_OTHER: @@ -580,7 +591,8 @@ static void hidp_recv_intr_frame(struct hidp_session *session, hidp_input_report(session, skb); if (session->hid) { - hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 1); + hidp_process_report(session, HID_INPUT_REPORT, + skb->data, skb->len, 1); BT_DBG("report len %d", skb->len); } } else { diff --git a/net/bluetooth/hidp/hidp.h b/net/bluetooth/hidp/hidp.h index 6162ce8606acc..5e2a5d54ad25a 100644 --- a/net/bluetooth/hidp/hidp.h +++ b/net/bluetooth/hidp/hidp.h @@ -24,6 +24,7 @@ #define __HIDP_H #include +#include #include #include #include @@ -177,6 +178,9 @@ struct hidp_session { /* Used in hidp_output_raw_report() */ int output_report_success; /* boolean */ + + /* temporary input buffer */ + u8 input_buf[HID_MAX_BUFFER_SIZE]; }; /* HIDP init defines */ diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index 6651a7797d46a..924af39089ead 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c @@ -2010,7 +2010,9 @@ static int ebt_size_mwt(struct compat_ebt_entry_mwt *match32, if (match_kern) match_kern->match_size = ret; - WARN_ON(type == EBT_COMPAT_TARGET && size_left); + if (WARN_ON(type == EBT_COMPAT_TARGET && size_left)) + return -EINVAL; + match32 = (struct compat_ebt_entry_mwt *) buf; } @@ -2067,6 +2069,15 @@ static int size_entry_mwt(struct ebt_entry *entry, const unsigned char *base, * * offsets are relative to beginning of struct ebt_entry (i.e., 0). */ + for (i = 0; i < 4 ; ++i) { + if (offsets[i] >= *total) + return -EINVAL; + if (i == 0) + continue; + if (offsets[i-1] > offsets[i]) + return -EINVAL; + } + for (i = 0, j = 1 ; j < 4 ; j++, i++) { struct compat_ebt_entry_mwt *match32; unsigned int size; diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index b4e9806af950c..ead11d28b5087 100644 --- a/net/ipv4/raw.c +++ b/net/ipv4/raw.c @@ -476,11 +476,16 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, u8 tos; int err; struct ip_options_data opt_copy; + int hdrincl; err = -EMSGSIZE; if (len > 0xFFFF) goto out; + /* hdrincl should be READ_ONCE(inet->hdrincl) + * but READ_ONCE() doesn't work with bit fields + */ + hdrincl = inet->hdrincl; /* * Check the flags. */ @@ -551,7 +556,7 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, /* Linux does not mangle headers on raw sockets, * so that IP options + IP_HDRINCL is non-sense. */ - if (inet->hdrincl) + if (hdrincl) goto done; if (ipc.opt->opt.srr) { if (!daddr) @@ -573,13 +578,13 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, flowi4_init_output(&fl4, ipc.oif, sk->sk_mark, tos, RT_SCOPE_UNIVERSE, - inet->hdrincl ? IPPROTO_RAW : sk->sk_protocol, + hdrincl ? IPPROTO_RAW : sk->sk_protocol, inet_sk_flowi_flags(sk) | FLOWI_FLAG_CAN_SLEEP | - (inet->hdrincl ? FLOWI_FLAG_KNOWN_NH : 0), + (hdrincl ? FLOWI_FLAG_KNOWN_NH : 0), daddr, saddr, 0, 0, sock_i_uid(sk)); - if (!inet->hdrincl) { + if (!hdrincl) { err = raw_probe_proto_opt(&fl4, msg); if (err) goto done; @@ -601,7 +606,7 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, goto do_confirm; back_from_confirm: - if (inet->hdrincl) + if (hdrincl) err = raw_send_hdrinc(sk, &fl4, msg->msg_iov, len, &rt, msg->msg_flags); diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 3a4b3b8810d92..5f7d9a8944da3 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -4590,6 +4590,7 @@ tcp_collapse(struct sock *sk, struct sk_buff_head *list, static void tcp_collapse_ofo_queue(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); + u32 range_truesize, sum_tiny = 0; struct sk_buff *skb = skb_peek(&tp->out_of_order_queue); struct sk_buff *head; u32 start, end; @@ -4599,6 +4600,7 @@ static void tcp_collapse_ofo_queue(struct sock *sk) start = TCP_SKB_CB(skb)->seq; end = TCP_SKB_CB(skb)->end_seq; + range_truesize = skb->truesize; head = skb; for (;;) { @@ -4613,14 +4615,24 @@ static void tcp_collapse_ofo_queue(struct sock *sk) if (!skb || after(TCP_SKB_CB(skb)->seq, end) || before(TCP_SKB_CB(skb)->end_seq, start)) { - tcp_collapse(sk, &tp->out_of_order_queue, - head, skb, start, end); + /* Do not attempt collapsing tiny skbs */ + if (range_truesize != head->truesize || + end - start >= SKB_WITH_OVERHEAD(SK_MEM_QUANTUM)) { + tcp_collapse(sk, &tp->out_of_order_queue, + head, skb, start, end); + } else { + sum_tiny += range_truesize; + if (sum_tiny > sk->sk_rcvbuf >> 3) + return; + } + head = skb; if (!skb) break; /* Start new segment */ start = TCP_SKB_CB(skb)->seq; end = TCP_SKB_CB(skb)->end_seq; + range_truesize = skb->truesize; } else { if (before(TCP_SKB_CB(skb)->seq, start)) start = TCP_SKB_CB(skb)->seq; @@ -4676,6 +4688,9 @@ static int tcp_prune_queue(struct sock *sk) else if (sk_under_memory_pressure(sk)) tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U * tp->advmss); + if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) + return 0; + tcp_collapse_ofo_queue(sk); if (!skb_queue_empty(&sk->sk_receive_queue)) tcp_collapse(sk, &sk->sk_receive_queue, diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c index 3840ff8a49163..cdf5bfe42567b 100644 --- a/net/netfilter/xt_qtaguid.c +++ b/net/netfilter/xt_qtaguid.c @@ -518,13 +518,11 @@ static struct tag_ref *get_tag_ref(tag_t full_tag, DR_DEBUG("qtaguid: get_tag_ref(0x%llx)\n", full_tag); - spin_lock_bh(&uid_tag_data_tree_lock); tr_entry = lookup_tag_ref(full_tag, &utd_entry); BUG_ON(IS_ERR_OR_NULL(utd_entry)); if (!tr_entry) tr_entry = new_tag_ref(full_tag, utd_entry); - spin_unlock_bh(&uid_tag_data_tree_lock); if (utd_res) *utd_res = utd_entry; DR_DEBUG("qtaguid: get_tag_ref(0x%llx) utd=%p tr=%p\n", @@ -2017,6 +2015,7 @@ static int ctrl_cmd_delete(const char *input) /* Delete socket tags */ spin_lock_bh(&sock_tag_list_lock); + spin_lock_bh(&uid_tag_data_tree_lock); node = rb_first(&sock_tag_tree); while (node) { st_entry = rb_entry(node, struct sock_tag, sock_node); @@ -2046,6 +2045,7 @@ static int ctrl_cmd_delete(const char *input) list_del(&st_entry->list); } } + spin_unlock_bh(&uid_tag_data_tree_lock); spin_unlock_bh(&sock_tag_list_lock); sock_tag_tree_erase(&st_to_free_tree); @@ -2250,10 +2250,12 @@ static int ctrl_cmd_tag(const char *input) full_tag = combine_atag_with_uid(acct_tag, uid); spin_lock_bh(&sock_tag_list_lock); + spin_lock_bh(&uid_tag_data_tree_lock); sock_tag_entry = get_sock_stat_nl(el_socket->sk); tag_ref_entry = get_tag_ref(full_tag, &uid_tag_data_entry); if (IS_ERR(tag_ref_entry)) { res = PTR_ERR(tag_ref_entry); + spin_unlock_bh(&uid_tag_data_tree_lock); spin_unlock_bh(&sock_tag_list_lock); goto err_put; } @@ -2280,9 +2282,14 @@ static int ctrl_cmd_tag(const char *input) pr_err("qtaguid: ctrl_tag(%s): " "socket tag alloc failed\n", input); + BUG_ON(tag_ref_entry->num_sock_tags <= 0); + tag_ref_entry->num_sock_tags--; + free_tag_ref_from_utd_entry(tag_ref_entry, + uid_tag_data_entry); + spin_unlock_bh(&uid_tag_data_tree_lock); spin_unlock_bh(&sock_tag_list_lock); res = -ENOMEM; - goto err_tag_unref_put; + goto err_put; } /* * Hold the sk refcount here to make sure the sk pointer cannot @@ -2293,7 +2300,6 @@ static int ctrl_cmd_tag(const char *input) sock_tag_entry->pid = current->tgid; sock_tag_entry->tag = combine_atag_with_uid(acct_tag, uid); - spin_lock_bh(&uid_tag_data_tree_lock); pqd_entry = proc_qtu_data_tree_search( &proc_qtu_data_tree, current->tgid); /* @@ -2311,11 +2317,11 @@ static int ctrl_cmd_tag(const char *input) else list_add(&sock_tag_entry->list, &pqd_entry->sock_tag_list); - spin_unlock_bh(&uid_tag_data_tree_lock); sock_tag_tree_insert(sock_tag_entry, &sock_tag_tree); atomic64_inc(&qtu_events.sockets_tagged); } + spin_unlock_bh(&uid_tag_data_tree_lock); spin_unlock_bh(&sock_tag_list_lock); /* We keep the ref to the sk until it is untagged */ CT_DEBUG("qtaguid: ctrl_tag(%s): done st@%p ...->sk_refcnt=%d\n", @@ -2324,10 +2330,6 @@ static int ctrl_cmd_tag(const char *input) sockfd_put(el_socket); return 0; -err_tag_unref_put: - BUG_ON(tag_ref_entry->num_sock_tags <= 0); - tag_ref_entry->num_sock_tags--; - free_tag_ref_from_utd_entry(tag_ref_entry, uid_tag_data_entry); err_put: CT_DEBUG("qtaguid: ctrl_tag(%s): done. ...->sk_refcnt=%d\n", input, atomic_read(&el_socket->sk->sk_refcnt) - 1); diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index d943b361569e3..aef16d085faaa 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -2567,6 +2567,7 @@ int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb, cb = &nlk->cb; memset(cb, 0, sizeof(*cb)); + cb->start = control->start; cb->dump = control->dump; cb->done = control->done; cb->nlh = nlh; @@ -2579,6 +2580,9 @@ int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb, mutex_unlock(nlk->cb_mutex); + if (cb->start) + cb->start(cb); + ret = netlink_dump(sk); sock_put(sk); diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index c7408dd8fd9a8..44feb83e0f24e 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -544,6 +544,20 @@ void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, } EXPORT_SYMBOL(genlmsg_put); +static int genl_lock_start(struct netlink_callback *cb) +{ + /* our ops are always const - netlink API doesn't propagate that */ + const struct genl_ops *ops = cb->data; + int rc = 0; + + if (ops->start) { + genl_lock(); + rc = ops->start(cb); + genl_unlock(); + } + return rc; +} + static int genl_lock_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct genl_ops *ops = cb->data; @@ -605,6 +619,7 @@ static int genl_family_rcv_msg(struct genl_family *family, struct netlink_dump_control c = { .module = family->module, .data = ops, + .start = genl_lock_start, .dump = genl_lock_dumpit, .done = genl_lock_done, }; @@ -616,6 +631,7 @@ static int genl_family_rcv_msg(struct genl_family *family, } else { struct netlink_dump_control c = { .module = family->module, + .start = genl_lock_start, .dump = ops->dumpit, .done = ops->done, }; diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index c1b23eef83ca9..ac7cf1faed18c 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -151,6 +151,10 @@ struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdreq_tlv(u8 tid, char *uri, pr_debug("uri: %s, len: %zu\n", uri, uri_len); + /* sdreq->tlv_len is u8, takes uri_len, + 3 for header, + 1 for NULL */ + if (WARN_ON_ONCE(uri_len > U8_MAX - 4)) + return NULL; + sdreq = kzalloc(sizeof(struct nfc_llcp_sdp_tlv), GFP_KERNEL); if (sdreq == NULL) return NULL; diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index f0c4d61f37c0f..532869d8bc359 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c @@ -59,7 +59,8 @@ static const struct nla_policy nfc_genl_policy[NFC_ATTR_MAX + 1] = { }; static const struct nla_policy nfc_sdp_genl_policy[NFC_SDP_ATTR_MAX + 1] = { - [NFC_SDP_ATTR_URI] = { .type = NLA_STRING }, + [NFC_SDP_ATTR_URI] = { .type = NLA_STRING, + .len = U8_MAX - 4 }, [NFC_SDP_ATTR_SAP] = { .type = NLA_U8 }, }; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index a6e641809097e..7d169ed1337fc 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -458,6 +459,14 @@ nl80211_match_policy[NL80211_SCHED_SCAN_MATCH_ATTR_MAX + 1] = { [NL80211_SCHED_SCAN_MATCH_ATTR_RSSI] = { .type = NLA_U32 }, }; +/* policy for packet pattern attributes */ +static const struct nla_policy +nl80211_packet_pattern_policy[MAX_NL80211_WOWLAN_PKTPAT + 1] = { + [NL80211_WOWLAN_PKTPAT_MASK] = { .type = NLA_BINARY, }, + [NL80211_WOWLAN_PKTPAT_PATTERN] = { .type = NLA_BINARY, }, + [NL80211_WOWLAN_PKTPAT_OFFSET] = { .type = NLA_U32 }, +}; + static int nl80211_prepare_wdev_dump(struct sk_buff *skb, struct netlink_callback *cb, struct cfg80211_registered_device **rdev, @@ -1754,20 +1763,22 @@ static const struct nla_policy txq_params_policy[NL80211_TXQ_ATTR_MAX + 1] = { static int parse_txq_params(struct nlattr *tb[], struct ieee80211_txq_params *txq_params) { + u8 ac; + if (!tb[NL80211_TXQ_ATTR_AC] || !tb[NL80211_TXQ_ATTR_TXOP] || !tb[NL80211_TXQ_ATTR_CWMIN] || !tb[NL80211_TXQ_ATTR_CWMAX] || !tb[NL80211_TXQ_ATTR_AIFS]) return -EINVAL; - txq_params->ac = nla_get_u8(tb[NL80211_TXQ_ATTR_AC]); + ac = nla_get_u8(tb[NL80211_TXQ_ATTR_AC]); txq_params->txop = nla_get_u16(tb[NL80211_TXQ_ATTR_TXOP]); txq_params->cwmin = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMIN]); txq_params->cwmax = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMAX]); txq_params->aifs = nla_get_u8(tb[NL80211_TXQ_ATTR_AIFS]); - if (txq_params->ac >= NL80211_NUM_ACS) + if (ac >= NL80211_NUM_ACS) return -EINVAL; - + txq_params->ac = array_index_nospec(ac, NL80211_NUM_ACS); return 0; } @@ -8180,7 +8191,7 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info) nla_for_each_nested(pat, tb[NL80211_WOWLAN_TRIG_PKT_PATTERN], rem) { nla_parse(pat_tb, MAX_NL80211_WOWLAN_PKTPAT, - nla_data(pat), nla_len(pat), NULL); + nla_data(pat), nla_len(pat), nl80211_packet_pattern_policy); err = -EINVAL; if (!pat_tb[NL80211_WOWLAN_PKTPAT_MASK] || !pat_tb[NL80211_WOWLAN_PKTPAT_PATTERN]) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index ff723b8122f51..e1e595651894a 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1564,31 +1564,34 @@ static int dump_one_policy(struct xfrm_policy *xp, int dir, int count, void *ptr static int xfrm_dump_policy_done(struct netlink_callback *cb) { - struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *) &cb->args[1]; + struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *)cb->args; - xfrm_policy_walk_done(walk); + if (cb->args[0]) + xfrm_policy_walk_done(walk); + return 0; +} + +static int xfrm_dump_policy_start(struct netlink_callback *cb) +{ + struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *)cb->args; + + BUILD_BUG_ON(sizeof(*walk) > sizeof(cb->args)); + + xfrm_policy_walk_init(walk, XFRM_POLICY_TYPE_ANY); return 0; } static int xfrm_dump_policy(struct sk_buff *skb, struct netlink_callback *cb) { struct net *net = sock_net(skb->sk); - struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *) &cb->args[1]; + struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *)cb->args; struct xfrm_dump_info info; - BUILD_BUG_ON(sizeof(struct xfrm_policy_walk) > - sizeof(cb->args) - sizeof(cb->args[0])); - info.in_skb = cb->skb; info.out_skb = skb; info.nlmsg_seq = cb->nlh->nlmsg_seq; info.nlmsg_flags = NLM_F_MULTI; - if (!cb->args[0]) { - cb->args[0] = 1; - xfrm_policy_walk_init(walk, XFRM_POLICY_TYPE_ANY); - } - (void) xfrm_policy_walk(net, walk, dump_one_policy, &info); return skb->len; @@ -2352,6 +2355,7 @@ static const struct nla_policy xfrma_policy[XFRMA_MAX+1] = { static const struct xfrm_link { int (*doit)(struct sk_buff *, struct nlmsghdr *, struct nlattr **); + int (*start)(struct netlink_callback *); int (*dump)(struct sk_buff *, struct netlink_callback *); int (*done)(struct netlink_callback *); } xfrm_dispatch[XFRM_NR_MSGTYPES] = { @@ -2363,6 +2367,7 @@ static const struct xfrm_link { [XFRM_MSG_NEWPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_add_policy }, [XFRM_MSG_DELPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_get_policy }, [XFRM_MSG_GETPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_get_policy, + .start = xfrm_dump_policy_start, .dump = xfrm_dump_policy, .done = xfrm_dump_policy_done }, [XFRM_MSG_ALLOCSPI - XFRM_MSG_BASE] = { .doit = xfrm_alloc_userspi }, @@ -2406,6 +2411,7 @@ static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) { struct netlink_dump_control c = { + .start = link->start, .dump = link->dump, .done = link->done, }; diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 419442cc36689..411549d484e34 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -141,23 +141,22 @@ static int valid_ecryptfs_desc(const char *ecryptfs_desc) */ static int valid_master_desc(const char *new_desc, const char *orig_desc) { - if (!memcmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN)) { - if (strlen(new_desc) == KEY_TRUSTED_PREFIX_LEN) - goto out; - if (orig_desc) - if (memcmp(new_desc, orig_desc, KEY_TRUSTED_PREFIX_LEN)) - goto out; - } else if (!memcmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN)) { - if (strlen(new_desc) == KEY_USER_PREFIX_LEN) - goto out; - if (orig_desc) - if (memcmp(new_desc, orig_desc, KEY_USER_PREFIX_LEN)) - goto out; - } else - goto out; + int prefix_len; + + if (!strncmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN)) + prefix_len = KEY_TRUSTED_PREFIX_LEN; + else if (!strncmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN)) + prefix_len = KEY_USER_PREFIX_LEN; + else + return -EINVAL; + + if (!new_desc[prefix_len]) + return -EINVAL; + + if (orig_desc && strncmp(new_desc, orig_desc, prefix_len)) + return -EINVAL; + return 0; -out: - return -EINVAL; } /* diff --git a/sound/isa/msnd/msnd_midi.c b/sound/isa/msnd/msnd_midi.c index ffc67fd80c23d..58e59cd3c95c0 100644 --- a/sound/isa/msnd/msnd_midi.c +++ b/sound/isa/msnd/msnd_midi.c @@ -120,24 +120,24 @@ void snd_msndmidi_input_read(void *mpuv) unsigned long flags; struct snd_msndmidi *mpu = mpuv; void *pwMIDQData = mpu->dev->mappedbase + MIDQ_DATA_BUFF; + u16 head, tail, size; spin_lock_irqsave(&mpu->input_lock, flags); - while (readw(mpu->dev->MIDQ + JQS_wTail) != - readw(mpu->dev->MIDQ + JQS_wHead)) { - u16 wTmp, val; - val = readw(pwMIDQData + 2 * readw(mpu->dev->MIDQ + JQS_wHead)); - - if (test_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, - &mpu->mode)) - snd_rawmidi_receive(mpu->substream_input, - (unsigned char *)&val, 1); - - wTmp = readw(mpu->dev->MIDQ + JQS_wHead) + 1; - if (wTmp > readw(mpu->dev->MIDQ + JQS_wSize)) - writew(0, mpu->dev->MIDQ + JQS_wHead); - else - writew(wTmp, mpu->dev->MIDQ + JQS_wHead); + head = readw(mpu->dev->MIDQ + JQS_wHead); + tail = readw(mpu->dev->MIDQ + JQS_wTail); + size = readw(mpu->dev->MIDQ + JQS_wSize); + if (head > size || tail > size) + goto out; + while (head != tail) { + unsigned char val = readw(pwMIDQData + 2 * head); + + if (test_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode)) + snd_rawmidi_receive(mpu->substream_input, &val, 1); + if (++head > size) + head = 0; + writew(head, mpu->dev->MIDQ + JQS_wHead); } + out: spin_unlock_irqrestore(&mpu->input_lock, flags); } EXPORT_SYMBOL(snd_msndmidi_input_read); diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c index 3a7946ebbe238..f7659ecbba0d8 100644 --- a/sound/isa/msnd/msnd_pinnacle.c +++ b/sound/isa/msnd/msnd_pinnacle.c @@ -170,23 +170,24 @@ static irqreturn_t snd_msnd_interrupt(int irq, void *dev_id) { struct snd_msnd *chip = dev_id; void *pwDSPQData = chip->mappedbase + DSPQ_DATA_BUFF; + u16 head, tail, size; /* Send ack to DSP */ /* inb(chip->io + HP_RXL); */ /* Evaluate queued DSP messages */ - while (readw(chip->DSPQ + JQS_wTail) != readw(chip->DSPQ + JQS_wHead)) { - u16 wTmp; - - snd_msnd_eval_dsp_msg(chip, - readw(pwDSPQData + 2 * readw(chip->DSPQ + JQS_wHead))); - - wTmp = readw(chip->DSPQ + JQS_wHead) + 1; - if (wTmp > readw(chip->DSPQ + JQS_wSize)) - writew(0, chip->DSPQ + JQS_wHead); - else - writew(wTmp, chip->DSPQ + JQS_wHead); + head = readw(chip->DSPQ + JQS_wHead); + tail = readw(chip->DSPQ + JQS_wTail); + size = readw(chip->DSPQ + JQS_wSize); + if (head > size || tail > size) + goto out; + while (head != tail) { + snd_msnd_eval_dsp_msg(chip, readw(pwDSPQData + 2 * head)); + if (++head > size) + head = 0; + writew(head, chip->DSPQ + JQS_wHead); } + out: /* Send ack to DSP */ inb(chip->io + HP_RXL); return IRQ_HANDLED; diff --git a/sound/soc/codecs/wcd_cpe_core.c b/sound/soc/codecs/wcd_cpe_core.c index 965caf8d4e8a1..a433bd2b18dcd 100644 --- a/sound/soc/codecs/wcd_cpe_core.c +++ b/sound/soc/codecs/wcd_cpe_core.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2015, 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -290,6 +290,14 @@ static int wcd_cpe_load_each_segment(struct wcd_cpe_core *core, goto done; } + if (phdr->p_filesz != split_fw->size) { + dev_err(core->dev, + "%s: %s size mismatch, phdr_size: 0x%x fw_size: 0x%zx", + __func__, split_fname, phdr->p_filesz, split_fw->size); + ret = -EINVAL; + goto done; + } + segment->cpe_addr = phdr->p_paddr; segment->size = phdr->p_filesz; segment->data = (u8 *) split_fw->data; diff --git a/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c index d467464f4e67d..bd8295448c75a 100644 --- a/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c +++ b/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c @@ -108,6 +108,7 @@ struct msm_compr_pdata { bool use_dsp_gapless_mode; struct msm_compr_dec_params *dec_params[MSM_FRONTEND_DAI_MAX]; struct msm_compr_ch_map *ch_map[MSM_FRONTEND_DAI_MAX]; + bool is_in_use[MSM_FRONTEND_DAI_MAX]; }; struct msm_compr_audio { @@ -921,11 +922,16 @@ static int msm_compr_open(struct snd_compr_stream *cstream) { struct snd_compr_runtime *runtime = cstream->runtime; struct snd_soc_pcm_runtime *rtd = cstream->private_data; - struct msm_compr_audio *prtd; + struct msm_compr_audio *prtd = NULL; struct msm_compr_pdata *pdata = snd_soc_platform_get_drvdata(rtd->platform); pr_debug("%s\n", __func__); + if (pdata->is_in_use[rtd->dai_link->be_id] == true) { + pr_err("%s: %s is already in use,err: %d ", + __func__, rtd->dai_link->cpu_dai_name, -EBUSY); + return -EBUSY; + } prtd = kzalloc(sizeof(struct msm_compr_audio), GFP_KERNEL); if (prtd == NULL) { pr_err("Failed to allocate memory for msm_compr_audio\n"); @@ -937,7 +943,7 @@ static int msm_compr_open(struct snd_compr_stream *cstream) pdata->cstream[rtd->dai_link->be_id] = cstream; pdata->audio_effects[rtd->dai_link->be_id] = kzalloc(sizeof(struct msm_compr_audio_effects), GFP_KERNEL); - if (!pdata->audio_effects[rtd->dai_link->be_id]) { + if (pdata->audio_effects[rtd->dai_link->be_id] == NULL) { pr_err("%s: Could not allocate memory for effects\n", __func__); pdata->cstream[rtd->dai_link->be_id] = NULL; kfree(prtd); @@ -945,10 +951,11 @@ static int msm_compr_open(struct snd_compr_stream *cstream) } pdata->dec_params[rtd->dai_link->be_id] = kzalloc(sizeof(struct msm_compr_dec_params), GFP_KERNEL); - if (!pdata->dec_params[rtd->dai_link->be_id]) { + if (pdata->dec_params[rtd->dai_link->be_id] == NULL) { pr_err("%s: Could not allocate memory for dec params\n", __func__); kfree(pdata->audio_effects[rtd->dai_link->be_id]); + pdata->audio_effects[rtd->dai_link->be_id] = NULL; pdata->cstream[rtd->dai_link->be_id] = NULL; kfree(prtd); return -ENOMEM; @@ -958,7 +965,9 @@ static int msm_compr_open(struct snd_compr_stream *cstream) if (!prtd->audio_client) { pr_err("%s: Could not allocate memory for client\n", __func__); kfree(pdata->audio_effects[rtd->dai_link->be_id]); + pdata->audio_effects[rtd->dai_link->be_id] = NULL; kfree(pdata->dec_params[rtd->dai_link->be_id]); + pdata->dec_params[rtd->dai_link->be_id] = NULL; pdata->cstream[rtd->dai_link->be_id] = NULL; kfree(prtd); return -ENOMEM; @@ -1016,7 +1025,7 @@ static int msm_compr_open(struct snd_compr_stream *cstream) } else { pr_err("%s: Unsupported stream type", __func__); } - + pdata->is_in_use[rtd->dai_link->be_id] = true; return 0; } @@ -1111,11 +1120,15 @@ static int msm_compr_free(struct snd_compr_stream *cstream) q6asm_audio_client_buf_free_contiguous(dir, ac); q6asm_audio_client_free(ac); - - kfree(pdata->audio_effects[soc_prtd->dai_link->be_id]); - pdata->audio_effects[soc_prtd->dai_link->be_id] = NULL; - kfree(pdata->dec_params[soc_prtd->dai_link->be_id]); - pdata->dec_params[soc_prtd->dai_link->be_id] = NULL; + if (pdata->audio_effects[soc_prtd->dai_link->be_id] != NULL) { + kfree(pdata->audio_effects[soc_prtd->dai_link->be_id]); + pdata->audio_effects[soc_prtd->dai_link->be_id] = NULL; + } + if (pdata->dec_params[soc_prtd->dai_link->be_id] != NULL) { + kfree(pdata->dec_params[soc_prtd->dai_link->be_id]); + pdata->dec_params[soc_prtd->dai_link->be_id] = NULL; + } + pdata->is_in_use[soc_prtd->dai_link->be_id] = false; kfree(prtd); runtime->private_data = NULL; @@ -2577,6 +2590,7 @@ static int msm_compr_probe(struct snd_soc_platform *platform) pdata->dec_params[i] = NULL; pdata->cstream[i] = NULL; pdata->ch_map[i] = NULL; + pdata->is_in_use[i] = false; } /* diff --git a/sound/soc/msm/qdsp6v2/q6asm.c b/sound/soc/msm/qdsp6v2/q6asm.c index 9bcf0017e954e..c3d3814272b5b 100755 --- a/sound/soc/msm/qdsp6v2/q6asm.c +++ b/sound/soc/msm/qdsp6v2/q6asm.c @@ -60,8 +60,13 @@ struct asm_mmap { }; static struct asm_mmap this_mmap; + +struct audio_session { + struct audio_client *ac; + spinlock_t session_lock; +}; /* session id: 0 reserved */ -static struct audio_client *session[SESSION_MAX+1]; +static struct audio_session session[SESSION_MAX + 1]; struct asm_no_wait_node { struct list_head list; @@ -141,6 +146,11 @@ static ssize_t audio_output_latency_dbgfs_read(struct file *file, pr_err("%s: out_buffer is null\n", __func__); return 0; } + if (count < OUT_BUFFER_SIZE) { + pr_err("%s: read size %d exceeds buf size %zd\n", __func__, + OUT_BUFFER_SIZE, count); + return 0; + } snprintf(out_buffer, OUT_BUFFER_SIZE, "%ld,%ld,%ld,%ld,%ld,%ld,",\ out_cold_tv.tv_sec, out_cold_tv.tv_usec, out_warm_tv.tv_sec,\ out_warm_tv.tv_usec, out_cont_tv.tv_sec, out_cont_tv.tv_usec); @@ -194,6 +204,11 @@ static ssize_t audio_input_latency_dbgfs_read(struct file *file, pr_err("%s: in_buffer is null\n", __func__); return 0; } + if (count < IN_BUFFER_SIZE) { + pr_err("%s: read size %d exceeds buf size %zd\n", __func__, + IN_BUFFER_SIZE, count); + return 0; + } snprintf(in_buffer, IN_BUFFER_SIZE, "%ld,%ld,",\ in_cont_tv.tv_sec, in_cont_tv.tv_usec); return simple_read_from_buffer(buf, IN_BUFFER_SIZE, ppos, @@ -398,8 +413,8 @@ static int q6asm_session_alloc(struct audio_client *ac) { int n; for (n = 1; n <= SESSION_MAX; n++) { - if (!session[n]) { - session[n] = ac; + if (!session[n].ac) { + session[n].ac = ac; return n; } } @@ -407,34 +422,41 @@ static int q6asm_session_alloc(struct audio_client *ac) return -ENOMEM; } -static bool q6asm_is_valid_audio_client(struct audio_client *ac) +static unsigned int q6asm_get_session_id_from_audio_client( + struct audio_client *ac) { - int n; + unsigned int n; + for (n = 1; n <= SESSION_MAX; n++) { - if (session[n] == ac) - return 1; + if (session[n].ac == ac) + return n; } return 0; } +static bool q6asm_is_valid_audio_client(struct audio_client *ac) +{ + return q6asm_get_session_id_from_audio_client(ac) ? 1 : 0; +} + static void q6asm_session_free(struct audio_client *ac) { - struct list_head *ptr, *next; - struct asm_no_wait_node *node; + int session_id; + unsigned long flags; pr_debug("%s: sessionid[%d]\n", __func__, ac->session); + session_id = ac->session; rtac_remove_popp_from_adm_devices(ac->session); - session[ac->session] = 0; + spin_lock_irqsave(&(session[session_id].session_lock), flags); + session[ac->session].ac = NULL; ac->session = 0; ac->perf_mode = LEGACY_PCM_MODE; ac->fptr_cache_ops = NULL; + ac->cb = NULL; + ac->priv = NULL; - list_for_each_safe(ptr, next, &ac->no_wait_que) { - node = list_entry(ptr, struct asm_no_wait_node, list); - list_del(&node->list); - kfree(node); - } - list_del(&ac->no_wait_que); + kfree(ac); + spin_unlock_irqrestore(&(session[session_id].session_lock), flags); return; } @@ -980,8 +1002,6 @@ void q6asm_audio_client_free(struct audio_client *ac) pr_debug("%s: APR De-Register\n", __func__); /*done:*/ - kfree(ac); - ac = NULL; mutex_unlock(&session_lock); return; @@ -1050,6 +1070,7 @@ struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv) if (n <= 0) { pr_err("%s: ASM Session alloc fail n=%d\n", __func__, n); mutex_unlock(&session_lock); + kfree(ac); goto fail_session; } ac->session = n; @@ -1127,7 +1148,6 @@ struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv) fail_apr1: q6asm_session_free(ac); fail_session: - kfree(ac); return NULL; } @@ -1142,11 +1162,11 @@ struct audio_client *q6asm_get_audio_client(int session_id) goto err; } - if (!session[session_id]) { + if (!(session[session_id].ac)) { pr_err("%s: session not active: %d\n", __func__, session_id); goto err; } - return session[session_id]; + return session[session_id].ac; err: return NULL; } @@ -1351,6 +1371,7 @@ static int32_t q6asm_srvc_callback(struct apr_client_data *data, void *priv) uint32_t i = IN; uint32_t *payload; unsigned long dsp_flags; + unsigned long flags; struct asm_buffer_node *buf_node = NULL; struct list_head *ptr, *next; @@ -1399,9 +1420,16 @@ static int32_t q6asm_srvc_callback(struct apr_client_data *data, void *priv) return 0; } sid = (data->token >> 8) & 0x0F; + if ((sid > 0 && sid <= SESSION_MAX)) + spin_lock_irqsave(&(session[sid].session_lock), flags); + ac = q6asm_get_audio_client(sid); if (!ac) { pr_debug("%s: session[%d] already freed\n", __func__, sid); + if ((sid > 0 && + sid <= SESSION_MAX)) + spin_unlock_irqrestore( + &(session[sid].session_lock), flags); return 0; } @@ -1451,6 +1479,10 @@ static int32_t q6asm_srvc_callback(struct apr_client_data *data, void *priv) __func__, payload[0]); break; } + if ((sid > 0 && + sid <= SESSION_MAX)) + spin_unlock_irqrestore( + &(session[sid].session_lock), flags); return 0; } @@ -1486,6 +1518,10 @@ static int32_t q6asm_srvc_callback(struct apr_client_data *data, void *priv) if (ac->cb) ac->cb(data->opcode, data->token, data->payload, ac->priv); + if ((sid > 0 && sid <= SESSION_MAX)) + spin_unlock_irqrestore( + &(session[sid].session_lock), flags); + return 0; } @@ -1512,7 +1548,8 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) uint32_t *payload; uint32_t wakeup_flag = 1; int32_t ret = 0; - + unsigned long flags; + int session_id; if (ac == NULL) { pr_err("%s: ac NULL\n", __func__); @@ -1522,15 +1559,21 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) pr_err("%s: data NULL\n", __func__); return -EINVAL; } - if (!q6asm_is_valid_audio_client(ac)) { - pr_err("%s: audio client pointer is invalid, ac = %pK\n", - __func__, ac); + + session_id = q6asm_get_session_id_from_audio_client(ac); + if (session_id <= 0) { + pr_err("%s: Session ID is invalid, session = %d\n", __func__, + session_id); return -EINVAL; } - if (ac->session <= 0 || ac->session > 8) { - pr_err("%s: Session ID is invalid, session = %d\n", __func__, - ac->session); + spin_lock_irqsave(&(session[session_id].session_lock), flags); + + if (!q6asm_is_valid_audio_client(ac)) { + pr_err("%s: audio client pointer is invalid, ac = %pK\n", + __func__, ac); + spin_unlock_irqrestore( + &(session[session_id].session_lock), flags); return -EINVAL; } @@ -1545,7 +1588,6 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) } if (data->opcode == RESET_EVENTS) { - mutex_lock(&ac->cmd_lock); atomic_set(&ac->reset, 1); if (ac->apr == NULL) ac->apr = ac->apr2; @@ -1561,7 +1603,8 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) atomic_set(&ac->cmd_state, 0); wake_up(&ac->time_wait); wake_up(&ac->cmd_wait); - mutex_unlock(&ac->cmd_lock); + spin_unlock_irqrestore( + &(session[session_id].session_lock), flags); return 0; } @@ -1572,9 +1615,16 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) data->dest_port); if ((data->opcode != ASM_DATA_EVENT_RENDERED_EOS) && (data->opcode != ASM_DATA_EVENT_EOS) && - (data->opcode != ASM_SESSION_EVENT_RX_UNDERFLOW)) + (data->opcode != ASM_SESSION_EVENT_RX_UNDERFLOW)) { + if (payload == NULL) { + pr_err("%s: payload is null\n", __func__); + spin_unlock_irqrestore( + &(session[session_id].session_lock), flags); + return -EINVAL; + } dev_vdbg(ac->dev, "%s: Payload = [0x%x] status[0x%x] opcode 0x%x\n", __func__, payload[0], payload[1], data->opcode); + } if (data->opcode == APR_BASIC_RSP_RESULT) { token = data->token; switch (payload[0]) { @@ -1596,6 +1646,8 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) ret = q6asm_is_valid_session(data, priv); if (ret != 0) { pr_err("%s: session invalid %d\n", __func__, ret); + spin_unlock_irqrestore( + &(session[session_id].session_lock), flags); return ret; } case ASM_SESSION_CMD_SET_MTMX_STRTR_PARAMS_V2: @@ -1623,6 +1675,9 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) atomic_set(&ac->cmd_state, -payload[1]); wake_up(&ac->cmd_wait); } + spin_unlock_irqrestore( + &(session[session_id].session_lock), + flags); return 0; } if (atomic_read(&ac->cmd_state) && wakeup_flag) { @@ -1643,6 +1698,9 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) atomic_set(&ac->mem_state, -payload[1]); wake_up(&ac->mem_wait); } + spin_unlock_irqrestore( + &(session[session_id].session_lock), + flags); return 0; } if (atomic_read(&ac->mem_state) && wakeup_flag) { @@ -1679,6 +1737,9 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) __func__, payload[0]); break; } + + spin_unlock_irqrestore( + &(session[session_id].session_lock), flags); return 0; } @@ -1692,6 +1753,9 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) if (port->buf == NULL) { pr_err("%s: Unexpected Write Done\n", __func__); + spin_unlock_irqrestore( + &(session[session_id].session_lock), + flags); return -EINVAL; } spin_lock_irqsave(&port->dsp_lock, dsp_flags); @@ -1705,6 +1769,9 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) __func__, payload[0], payload[1]); spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + spin_unlock_irqrestore( + &(session[session_id].session_lock), + flags); return -EINVAL; } token = data->token; @@ -1776,6 +1843,9 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) if (ac->io_mode & SYNC_IO_MODE) { if (port->buf == NULL) { pr_err("%s: Unexpected Write Done\n", __func__); + spin_unlock_irqrestore( + &(session[session_id].session_lock), + flags); return -EINVAL; } spin_lock_irqsave(&port->dsp_lock, dsp_flags); @@ -1844,7 +1914,8 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) if (ac->cb) ac->cb(data->opcode, data->token, data->payload, ac->priv); - + spin_unlock_irqrestore( + &(session[session_id].session_lock), flags); return 0; } @@ -1986,11 +2057,16 @@ int q6asm_is_dsp_buf_avail(int dir, struct audio_client *ac) static void __q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, uint32_t pkt_size, uint32_t cmd_flg, uint32_t stream_id) { + unsigned long flags; + dev_vdbg(ac->dev, "%s: pkt_size=%d cmd_flg=%d session=%d stream_id=%d\n", __func__, pkt_size, cmd_flg, ac->session, stream_id); mutex_lock(&ac->cmd_lock); + spin_lock_irqsave(&(session[ac->session].session_lock), flags); if (ac->apr == NULL) { pr_err("%s: AC APR handle NULL", __func__); + spin_unlock_irqrestore( + &(session[ac->session].session_lock), flags); mutex_unlock(&ac->cmd_lock); return; } @@ -2008,6 +2084,8 @@ static void __q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, hdr->token = ac->session; } hdr->pkt_size = pkt_size; + spin_unlock_irqrestore( + &(session[ac->session].session_lock), flags); mutex_unlock(&ac->cmd_lock); return; } @@ -6500,7 +6578,7 @@ int q6asm_get_apr_service_id(int session_id) return -EINVAL; } - return ((struct apr_svc *)session[session_id]->apr)->id; + return ((struct apr_svc *)(session[session_id].ac)->apr)->id; } int q6asm_get_asm_topology(void) @@ -6718,7 +6796,10 @@ static int __init q6asm_init(void) int lcnt, ret; pr_debug("%s:\n", __func__); - memset(session, 0, sizeof(session)); + memset(session, 0, sizeof(struct audio_session) * + (SESSION_MAX + 1)); + for (lcnt = 0; lcnt <= SESSION_MAX; lcnt++) + spin_lock_init(&(session[lcnt].session_lock)); set_custom_topology = 1; /*setup common client used for cal mem map */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 83b13d6f6096b..5f5b4fb342a48 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3653,7 +3653,11 @@ int snd_soc_register_card(struct snd_soc_card *card) if (card->rtd == NULL) return -ENOMEM; card->num_rtd = 0; - card->rtd_aux = &card->rtd[card->num_links]; + + if (card->num_aux_devs > 0) + card->rtd_aux = &card->rtd[card->num_links]; + else + card->rtd_aux = NULL; for (i = 0; i < card->num_links; i++) card->rtd[i].dai_link = &card->dai_link[i];