GNU Linux-libre 4.4.283-gnu1
[releases.git] / arch / s390 / kernel / crash_dump.c
1 /*
2  * S390 kdump implementation
3  *
4  * Copyright IBM Corp. 2011
5  * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
6  */
7
8 #include <linux/crash_dump.h>
9 #include <asm/lowcore.h>
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/gfp.h>
13 #include <linux/slab.h>
14 #include <linux/bootmem.h>
15 #include <linux/elf.h>
16 #include <linux/memblock.h>
17 #include <asm/os_info.h>
18 #include <asm/elf.h>
19 #include <asm/ipl.h>
20 #include <asm/sclp.h>
21
22 #define PTR_ADD(x, y) (((char *) (x)) + ((unsigned long) (y)))
23 #define PTR_SUB(x, y) (((char *) (x)) - ((unsigned long) (y)))
24 #define PTR_DIFF(x, y) ((unsigned long)(((char *) (x)) - ((unsigned long) (y))))
25
26 #define LINUX_NOTE_NAME "LINUX"
27
28 static struct memblock_region oldmem_region;
29
30 static struct memblock_type oldmem_type = {
31         .cnt = 1,
32         .max = 1,
33         .total_size = 0,
34         .regions = &oldmem_region,
35 };
36
37 struct dump_save_areas dump_save_areas;
38
39 /*
40  * Return physical address for virtual address
41  */
42 static inline void *load_real_addr(void *addr)
43 {
44         unsigned long real_addr;
45
46         asm volatile(
47                    "    lra     %0,0(%1)\n"
48                    "    jz      0f\n"
49                    "    la      %0,0\n"
50                    "0:"
51                    : "=a" (real_addr) : "a" (addr) : "cc");
52         return (void *)real_addr;
53 }
54
55 /*
56  * Copy real to virtual or real memory
57  */
58 static int copy_from_realmem(void *dest, void *src, size_t count)
59 {
60         unsigned long size;
61
62         if (!count)
63                 return 0;
64         if (!is_vmalloc_or_module_addr(dest))
65                 return memcpy_real(dest, src, count);
66         do {
67                 size = min(count, PAGE_SIZE - (__pa(dest) & ~PAGE_MASK));
68                 if (memcpy_real(load_real_addr(dest), src, size))
69                         return -EFAULT;
70                 count -= size;
71                 dest += size;
72                 src += size;
73         } while (count);
74         return 0;
75 }
76
77 /*
78  * Pointer to ELF header in new kernel
79  */
80 static void *elfcorehdr_newmem;
81
82 /*
83  * Copy one page from zfcpdump "oldmem"
84  *
85  * For pages below HSA size memory from the HSA is copied. Otherwise
86  * real memory copy is used.
87  */
88 static ssize_t copy_oldmem_page_zfcpdump(char *buf, size_t csize,
89                                          unsigned long src, int userbuf)
90 {
91         int rc;
92
93         if (src < sclp.hsa_size) {
94                 rc = memcpy_hsa(buf, src, csize, userbuf);
95         } else {
96                 if (userbuf)
97                         rc = copy_to_user_real((void __force __user *) buf,
98                                                (void *) src, csize);
99                 else
100                         rc = memcpy_real(buf, (void *) src, csize);
101         }
102         return rc ? rc : csize;
103 }
104
105 /*
106  * Copy one page from kdump "oldmem"
107  *
108  * For the kdump reserved memory this functions performs a swap operation:
109  *  - [OLDMEM_BASE - OLDMEM_BASE + OLDMEM_SIZE] is mapped to [0 - OLDMEM_SIZE].
110  *  - [0 - OLDMEM_SIZE] is mapped to [OLDMEM_BASE - OLDMEM_BASE + OLDMEM_SIZE]
111  */
112 static ssize_t copy_oldmem_page_kdump(char *buf, size_t csize,
113                                       unsigned long src, int userbuf)
114
115 {
116         int rc;
117
118         if (src < OLDMEM_SIZE)
119                 src += OLDMEM_BASE;
120         else if (src > OLDMEM_BASE &&
121                  src < OLDMEM_BASE + OLDMEM_SIZE)
122                 src -= OLDMEM_BASE;
123         if (userbuf)
124                 rc = copy_to_user_real((void __force __user *) buf,
125                                        (void *) src, csize);
126         else
127                 rc = copy_from_realmem(buf, (void *) src, csize);
128         return (rc == 0) ? rc : csize;
129 }
130
131 /*
132  * Copy one page from "oldmem"
133  */
134 ssize_t copy_oldmem_page(unsigned long pfn, char *buf, size_t csize,
135                          unsigned long offset, int userbuf)
136 {
137         unsigned long src;
138
139         if (!csize)
140                 return 0;
141         src = (pfn << PAGE_SHIFT) + offset;
142         if (OLDMEM_BASE)
143                 return copy_oldmem_page_kdump(buf, csize, src, userbuf);
144         else
145                 return copy_oldmem_page_zfcpdump(buf, csize, src, userbuf);
146 }
147
148 /*
149  * Remap "oldmem" for kdump
150  *
151  * For the kdump reserved memory this functions performs a swap operation:
152  * [0 - OLDMEM_SIZE] is mapped to [OLDMEM_BASE - OLDMEM_BASE + OLDMEM_SIZE]
153  */
154 static int remap_oldmem_pfn_range_kdump(struct vm_area_struct *vma,
155                                         unsigned long from, unsigned long pfn,
156                                         unsigned long size, pgprot_t prot)
157 {
158         unsigned long size_old;
159         int rc;
160
161         if (pfn < OLDMEM_SIZE >> PAGE_SHIFT) {
162                 size_old = min(size, OLDMEM_SIZE - (pfn << PAGE_SHIFT));
163                 rc = remap_pfn_range(vma, from,
164                                      pfn + (OLDMEM_BASE >> PAGE_SHIFT),
165                                      size_old, prot);
166                 if (rc || size == size_old)
167                         return rc;
168                 size -= size_old;
169                 from += size_old;
170                 pfn += size_old >> PAGE_SHIFT;
171         }
172         return remap_pfn_range(vma, from, pfn, size, prot);
173 }
174
175 /*
176  * Remap "oldmem" for zfcpdump
177  *
178  * We only map available memory above HSA size. Memory below HSA size
179  * is read on demand using the copy_oldmem_page() function.
180  */
181 static int remap_oldmem_pfn_range_zfcpdump(struct vm_area_struct *vma,
182                                            unsigned long from,
183                                            unsigned long pfn,
184                                            unsigned long size, pgprot_t prot)
185 {
186         unsigned long hsa_end = sclp.hsa_size;
187         unsigned long size_hsa;
188
189         if (pfn < hsa_end >> PAGE_SHIFT) {
190                 size_hsa = min(size, hsa_end - (pfn << PAGE_SHIFT));
191                 if (size == size_hsa)
192                         return 0;
193                 size -= size_hsa;
194                 from += size_hsa;
195                 pfn += size_hsa >> PAGE_SHIFT;
196         }
197         return remap_pfn_range(vma, from, pfn, size, prot);
198 }
199
200 /*
201  * Remap "oldmem" for kdump or zfcpdump
202  */
203 int remap_oldmem_pfn_range(struct vm_area_struct *vma, unsigned long from,
204                            unsigned long pfn, unsigned long size, pgprot_t prot)
205 {
206         if (OLDMEM_BASE)
207                 return remap_oldmem_pfn_range_kdump(vma, from, pfn, size, prot);
208         else
209                 return remap_oldmem_pfn_range_zfcpdump(vma, from, pfn, size,
210                                                        prot);
211 }
212
213 /*
214  * Copy memory from old kernel
215  */
216 int copy_from_oldmem(void *dest, void *src, size_t count)
217 {
218         unsigned long copied = 0;
219         int rc;
220
221         if (OLDMEM_BASE) {
222                 if ((unsigned long) src < OLDMEM_SIZE) {
223                         copied = min(count, OLDMEM_SIZE - (unsigned long) src);
224                         rc = copy_from_realmem(dest, src + OLDMEM_BASE, copied);
225                         if (rc)
226                                 return rc;
227                 }
228         } else {
229                 unsigned long hsa_end = sclp.hsa_size;
230                 if ((unsigned long) src < hsa_end) {
231                         copied = min(count, hsa_end - (unsigned long) src);
232                         rc = memcpy_hsa(dest, (unsigned long) src, copied, 0);
233                         if (rc)
234                                 return rc;
235                 }
236         }
237         return copy_from_realmem(dest + copied, src + copied, count - copied);
238 }
239
240 /*
241  * Alloc memory and panic in case of ENOMEM
242  */
243 static void *kzalloc_panic(int len)
244 {
245         void *rc;
246
247         rc = kzalloc(len, GFP_KERNEL);
248         if (!rc)
249                 panic("s390 kdump kzalloc (%d) failed", len);
250         return rc;
251 }
252
253 /*
254  * Initialize ELF note
255  */
256 static void *nt_init(void *buf, Elf64_Word type, void *desc, int d_len,
257                      const char *name)
258 {
259         Elf64_Nhdr *note;
260         u64 len;
261
262         note = (Elf64_Nhdr *)buf;
263         note->n_namesz = strlen(name) + 1;
264         note->n_descsz = d_len;
265         note->n_type = type;
266         len = sizeof(Elf64_Nhdr);
267
268         memcpy(buf + len, name, note->n_namesz);
269         len = roundup(len + note->n_namesz, 4);
270
271         memcpy(buf + len, desc, note->n_descsz);
272         len = roundup(len + note->n_descsz, 4);
273
274         return PTR_ADD(buf, len);
275 }
276
277 /*
278  * Initialize prstatus note
279  */
280 static void *nt_prstatus(void *ptr, struct save_area *sa)
281 {
282         struct elf_prstatus nt_prstatus;
283         static int cpu_nr = 1;
284
285         memset(&nt_prstatus, 0, sizeof(nt_prstatus));
286         memcpy(&nt_prstatus.pr_reg.gprs, sa->gp_regs, sizeof(sa->gp_regs));
287         memcpy(&nt_prstatus.pr_reg.psw, sa->psw, sizeof(sa->psw));
288         memcpy(&nt_prstatus.pr_reg.acrs, sa->acc_regs, sizeof(sa->acc_regs));
289         nt_prstatus.pr_pid = cpu_nr;
290         cpu_nr++;
291
292         return nt_init(ptr, NT_PRSTATUS, &nt_prstatus, sizeof(nt_prstatus),
293                          "CORE");
294 }
295
296 /*
297  * Initialize fpregset (floating point) note
298  */
299 static void *nt_fpregset(void *ptr, struct save_area *sa)
300 {
301         elf_fpregset_t nt_fpregset;
302
303         memset(&nt_fpregset, 0, sizeof(nt_fpregset));
304         memcpy(&nt_fpregset.fpc, &sa->fp_ctrl_reg, sizeof(sa->fp_ctrl_reg));
305         memcpy(&nt_fpregset.fprs, &sa->fp_regs, sizeof(sa->fp_regs));
306
307         return nt_init(ptr, NT_PRFPREG, &nt_fpregset, sizeof(nt_fpregset),
308                        "CORE");
309 }
310
311 /*
312  * Initialize timer note
313  */
314 static void *nt_s390_timer(void *ptr, struct save_area *sa)
315 {
316         return nt_init(ptr, NT_S390_TIMER, &sa->timer, sizeof(sa->timer),
317                          LINUX_NOTE_NAME);
318 }
319
320 /*
321  * Initialize TOD clock comparator note
322  */
323 static void *nt_s390_tod_cmp(void *ptr, struct save_area *sa)
324 {
325         return nt_init(ptr, NT_S390_TODCMP, &sa->clk_cmp,
326                        sizeof(sa->clk_cmp), LINUX_NOTE_NAME);
327 }
328
329 /*
330  * Initialize TOD programmable register note
331  */
332 static void *nt_s390_tod_preg(void *ptr, struct save_area *sa)
333 {
334         return nt_init(ptr, NT_S390_TODPREG, &sa->tod_reg,
335                        sizeof(sa->tod_reg), LINUX_NOTE_NAME);
336 }
337
338 /*
339  * Initialize control register note
340  */
341 static void *nt_s390_ctrs(void *ptr, struct save_area *sa)
342 {
343         return nt_init(ptr, NT_S390_CTRS, &sa->ctrl_regs,
344                        sizeof(sa->ctrl_regs), LINUX_NOTE_NAME);
345 }
346
347 /*
348  * Initialize prefix register note
349  */
350 static void *nt_s390_prefix(void *ptr, struct save_area *sa)
351 {
352         return nt_init(ptr, NT_S390_PREFIX, &sa->pref_reg,
353                          sizeof(sa->pref_reg), LINUX_NOTE_NAME);
354 }
355
356 /*
357  * Initialize vxrs high note (full 128 bit VX registers 16-31)
358  */
359 static void *nt_s390_vx_high(void *ptr, __vector128 *vx_regs)
360 {
361         return nt_init(ptr, NT_S390_VXRS_HIGH, &vx_regs[16],
362                        16 * sizeof(__vector128), LINUX_NOTE_NAME);
363 }
364
365 /*
366  * Initialize vxrs low note (lower halves of VX registers 0-15)
367  */
368 static void *nt_s390_vx_low(void *ptr, __vector128 *vx_regs)
369 {
370         Elf64_Nhdr *note;
371         u64 len;
372         int i;
373
374         note = (Elf64_Nhdr *)ptr;
375         note->n_namesz = strlen(LINUX_NOTE_NAME) + 1;
376         note->n_descsz = 16 * 8;
377         note->n_type = NT_S390_VXRS_LOW;
378         len = sizeof(Elf64_Nhdr);
379
380         memcpy(ptr + len, LINUX_NOTE_NAME, note->n_namesz);
381         len = roundup(len + note->n_namesz, 4);
382
383         ptr += len;
384         /* Copy lower halves of SIMD registers 0-15 */
385         for (i = 0; i < 16; i++) {
386                 memcpy(ptr, &vx_regs[i].u[2], 8);
387                 ptr += 8;
388         }
389         return ptr;
390 }
391
392 /*
393  * Fill ELF notes for one CPU with save area registers
394  */
395 void *fill_cpu_elf_notes(void *ptr, struct save_area *sa, __vector128 *vx_regs)
396 {
397         ptr = nt_prstatus(ptr, sa);
398         ptr = nt_fpregset(ptr, sa);
399         ptr = nt_s390_timer(ptr, sa);
400         ptr = nt_s390_tod_cmp(ptr, sa);
401         ptr = nt_s390_tod_preg(ptr, sa);
402         ptr = nt_s390_ctrs(ptr, sa);
403         ptr = nt_s390_prefix(ptr, sa);
404         if (MACHINE_HAS_VX && vx_regs) {
405                 ptr = nt_s390_vx_low(ptr, vx_regs);
406                 ptr = nt_s390_vx_high(ptr, vx_regs);
407         }
408         return ptr;
409 }
410
411 /*
412  * Initialize prpsinfo note (new kernel)
413  */
414 static void *nt_prpsinfo(void *ptr)
415 {
416         struct elf_prpsinfo prpsinfo;
417
418         memset(&prpsinfo, 0, sizeof(prpsinfo));
419         prpsinfo.pr_sname = 'R';
420         strcpy(prpsinfo.pr_fname, "vmlinux");
421         return nt_init(ptr, NT_PRPSINFO, &prpsinfo, sizeof(prpsinfo),
422                        KEXEC_CORE_NOTE_NAME);
423 }
424
425 /*
426  * Get vmcoreinfo using lowcore->vmcore_info (new kernel)
427  */
428 static void *get_vmcoreinfo_old(unsigned long *size)
429 {
430         char nt_name[11], *vmcoreinfo;
431         Elf64_Nhdr note;
432         void *addr;
433
434         if (copy_from_oldmem(&addr, &S390_lowcore.vmcore_info, sizeof(addr)))
435                 return NULL;
436         memset(nt_name, 0, sizeof(nt_name));
437         if (copy_from_oldmem(&note, addr, sizeof(note)))
438                 return NULL;
439         if (copy_from_oldmem(nt_name, addr + sizeof(note), sizeof(nt_name) - 1))
440                 return NULL;
441         if (strcmp(nt_name, "VMCOREINFO") != 0)
442                 return NULL;
443         vmcoreinfo = kzalloc_panic(note.n_descsz);
444         if (copy_from_oldmem(vmcoreinfo, addr + 24, note.n_descsz))
445                 return NULL;
446         *size = note.n_descsz;
447         return vmcoreinfo;
448 }
449
450 /*
451  * Initialize vmcoreinfo note (new kernel)
452  */
453 static void *nt_vmcoreinfo(void *ptr)
454 {
455         unsigned long size;
456         void *vmcoreinfo;
457
458         vmcoreinfo = os_info_old_entry(OS_INFO_VMCOREINFO, &size);
459         if (!vmcoreinfo)
460                 vmcoreinfo = get_vmcoreinfo_old(&size);
461         if (!vmcoreinfo)
462                 return ptr;
463         return nt_init(ptr, 0, vmcoreinfo, size, "VMCOREINFO");
464 }
465
466 /*
467  * Initialize final note (needed for /proc/vmcore code)
468  */
469 static void *nt_final(void *ptr)
470 {
471         Elf64_Nhdr *note;
472
473         note = (Elf64_Nhdr *) ptr;
474         note->n_namesz = 0;
475         note->n_descsz = 0;
476         note->n_type = 0;
477         return PTR_ADD(ptr, sizeof(Elf64_Nhdr));
478 }
479
480 /*
481  * Initialize ELF header (new kernel)
482  */
483 static void *ehdr_init(Elf64_Ehdr *ehdr, int mem_chunk_cnt)
484 {
485         memset(ehdr, 0, sizeof(*ehdr));
486         memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
487         ehdr->e_ident[EI_CLASS] = ELFCLASS64;
488         ehdr->e_ident[EI_DATA] = ELFDATA2MSB;
489         ehdr->e_ident[EI_VERSION] = EV_CURRENT;
490         memset(ehdr->e_ident + EI_PAD, 0, EI_NIDENT - EI_PAD);
491         ehdr->e_type = ET_CORE;
492         ehdr->e_machine = EM_S390;
493         ehdr->e_version = EV_CURRENT;
494         ehdr->e_phoff = sizeof(Elf64_Ehdr);
495         ehdr->e_ehsize = sizeof(Elf64_Ehdr);
496         ehdr->e_phentsize = sizeof(Elf64_Phdr);
497         ehdr->e_phnum = mem_chunk_cnt + 1;
498         return ehdr + 1;
499 }
500
501 /*
502  * Return CPU count for ELF header (new kernel)
503  */
504 static int get_cpu_cnt(void)
505 {
506         int i, cpus = 0;
507
508         for (i = 0; i < dump_save_areas.count; i++) {
509                 if (dump_save_areas.areas[i]->sa.pref_reg == 0)
510                         continue;
511                 cpus++;
512         }
513         return cpus;
514 }
515
516 /*
517  * Return memory chunk count for ELF header (new kernel)
518  */
519 static int get_mem_chunk_cnt(void)
520 {
521         int cnt = 0;
522         u64 idx;
523
524         for_each_mem_range(idx, &memblock.physmem, &oldmem_type, NUMA_NO_NODE,
525                            MEMBLOCK_NONE, NULL, NULL, NULL)
526                 cnt++;
527         return cnt;
528 }
529
530 /*
531  * Initialize ELF loads (new kernel)
532  */
533 static void loads_init(Elf64_Phdr *phdr, u64 loads_offset)
534 {
535         phys_addr_t start, end;
536         u64 idx;
537
538         for_each_mem_range(idx, &memblock.physmem, &oldmem_type, NUMA_NO_NODE,
539                            MEMBLOCK_NONE, &start, &end, NULL) {
540                 phdr->p_filesz = end - start;
541                 phdr->p_type = PT_LOAD;
542                 phdr->p_offset = start;
543                 phdr->p_vaddr = start;
544                 phdr->p_paddr = start;
545                 phdr->p_memsz = end - start;
546                 phdr->p_flags = PF_R | PF_W | PF_X;
547                 phdr->p_align = PAGE_SIZE;
548                 phdr++;
549         }
550 }
551
552 /*
553  * Initialize notes (new kernel)
554  */
555 static void *notes_init(Elf64_Phdr *phdr, void *ptr, u64 notes_offset)
556 {
557         struct save_area_ext *sa_ext;
558         void *ptr_start = ptr;
559         int i;
560
561         ptr = nt_prpsinfo(ptr);
562
563         for (i = 0; i < dump_save_areas.count; i++) {
564                 sa_ext = dump_save_areas.areas[i];
565                 if (sa_ext->sa.pref_reg == 0)
566                         continue;
567                 ptr = fill_cpu_elf_notes(ptr, &sa_ext->sa, sa_ext->vx_regs);
568         }
569         ptr = nt_vmcoreinfo(ptr);
570         ptr = nt_final(ptr);
571         memset(phdr, 0, sizeof(*phdr));
572         phdr->p_type = PT_NOTE;
573         phdr->p_offset = notes_offset;
574         phdr->p_filesz = (unsigned long) PTR_SUB(ptr, ptr_start);
575         phdr->p_memsz = phdr->p_filesz;
576         return ptr;
577 }
578
579 /*
580  * Create ELF core header (new kernel)
581  */
582 int elfcorehdr_alloc(unsigned long long *addr, unsigned long long *size)
583 {
584         Elf64_Phdr *phdr_notes, *phdr_loads;
585         int mem_chunk_cnt;
586         void *ptr, *hdr;
587         u32 alloc_size;
588         u64 hdr_off;
589
590         /* If we are not in kdump or zfcpdump mode return */
591         if (!OLDMEM_BASE && ipl_info.type != IPL_TYPE_FCP_DUMP)
592                 return 0;
593         /* If elfcorehdr= has been passed via cmdline, we use that one */
594         if (elfcorehdr_addr != ELFCORE_ADDR_MAX)
595                 return 0;
596         /* If we cannot get HSA size for zfcpdump return error */
597         if (ipl_info.type == IPL_TYPE_FCP_DUMP && !sclp.hsa_size)
598                 return -ENODEV;
599
600         /* For kdump, exclude previous crashkernel memory */
601         if (OLDMEM_BASE) {
602                 oldmem_region.base = OLDMEM_BASE;
603                 oldmem_region.size = OLDMEM_SIZE;
604                 oldmem_type.total_size = OLDMEM_SIZE;
605         }
606
607         mem_chunk_cnt = get_mem_chunk_cnt();
608
609         alloc_size = 0x1000 + get_cpu_cnt() * 0x4a0 +
610                 mem_chunk_cnt * sizeof(Elf64_Phdr);
611         hdr = kzalloc_panic(alloc_size);
612         /* Init elf header */
613         ptr = ehdr_init(hdr, mem_chunk_cnt);
614         /* Init program headers */
615         phdr_notes = ptr;
616         ptr = PTR_ADD(ptr, sizeof(Elf64_Phdr));
617         phdr_loads = ptr;
618         ptr = PTR_ADD(ptr, sizeof(Elf64_Phdr) * mem_chunk_cnt);
619         /* Init notes */
620         hdr_off = PTR_DIFF(ptr, hdr);
621         ptr = notes_init(phdr_notes, ptr, ((unsigned long) hdr) + hdr_off);
622         /* Init loads */
623         hdr_off = PTR_DIFF(ptr, hdr);
624         loads_init(phdr_loads, hdr_off);
625         *addr = (unsigned long long) hdr;
626         elfcorehdr_newmem = hdr;
627         *size = (unsigned long long) hdr_off;
628         BUG_ON(elfcorehdr_size > alloc_size);
629         return 0;
630 }
631
632 /*
633  * Free ELF core header (new kernel)
634  */
635 void elfcorehdr_free(unsigned long long addr)
636 {
637         if (!elfcorehdr_newmem)
638                 return;
639         kfree((void *)(unsigned long)addr);
640 }
641
642 /*
643  * Read from ELF header
644  */
645 ssize_t elfcorehdr_read(char *buf, size_t count, u64 *ppos)
646 {
647         void *src = (void *)(unsigned long)*ppos;
648
649         src = elfcorehdr_newmem ? src : src - OLDMEM_BASE;
650         memcpy(buf, src, count);
651         *ppos += count;
652         return count;
653 }
654
655 /*
656  * Read from ELF notes data
657  */
658 ssize_t elfcorehdr_read_notes(char *buf, size_t count, u64 *ppos)
659 {
660         void *src = (void *)(unsigned long)*ppos;
661         int rc;
662
663         if (elfcorehdr_newmem) {
664                 memcpy(buf, src, count);
665         } else {
666                 rc = copy_from_oldmem(buf, src, count);
667                 if (rc)
668                         return rc;
669         }
670         *ppos += count;
671         return count;
672 }