xref: /DragonOS/kernel/src/mm/mod.rs (revision 45626c859f95054b76d8b59afcbd24c6b235026f)
1 use alloc::sync::Arc;
2 use system_error::SystemError;
3 
4 use crate::{arch::MMArch, include::bindings::bindings::PAGE_OFFSET};
5 
6 use core::{
7     cmp,
8     fmt::Debug,
9     intrinsics::unlikely,
10     ops::{Add, AddAssign, Sub, SubAssign},
11     ptr,
12     sync::atomic::{AtomicBool, Ordering},
13 };
14 
15 use self::{
16     allocator::page_frame::{VirtPageFrame, VirtPageFrameIter},
17     page::round_up_to_page_size,
18     ucontext::{AddressSpace, UserMapper},
19 };
20 
21 pub mod allocator;
22 pub mod c_adapter;
23 pub mod kernel_mapper;
24 pub mod memblock;
25 pub mod mmio_buddy;
26 pub mod no_init;
27 pub mod page;
28 pub mod percpu;
29 pub mod syscall;
30 pub mod ucontext;
31 
32 /// 内核INIT进程的用户地址空间结构体(仅在process_init中初始化)
33 static mut __INITIAL_PROCESS_ADDRESS_SPACE: Option<Arc<AddressSpace>> = None;
34 
35 /// 获取内核INIT进程的用户地址空间结构体
36 #[allow(non_snake_case)]
37 #[inline(always)]
38 pub fn INITIAL_PROCESS_ADDRESS_SPACE() -> Arc<AddressSpace> {
39     unsafe {
40         return __INITIAL_PROCESS_ADDRESS_SPACE
41             .as_ref()
42             .expect("INITIAL_PROCESS_ADDRESS_SPACE is null")
43             .clone();
44     }
45 }
46 
47 /// 设置内核INIT进程的用户地址空间结构体全局变量
48 #[allow(non_snake_case)]
49 pub unsafe fn set_INITIAL_PROCESS_ADDRESS_SPACE(address_space: Arc<AddressSpace>) {
50     static INITIALIZED: AtomicBool = AtomicBool::new(false);
51     if INITIALIZED
52         .compare_exchange(false, true, Ordering::SeqCst, Ordering::Acquire)
53         .is_err()
54     {
55         panic!("INITIAL_PROCESS_ADDRESS_SPACE is already initialized");
56     }
57     __INITIAL_PROCESS_ADDRESS_SPACE = Some(address_space);
58 }
59 
60 /// @brief 将内核空间的虚拟地址转换为物理地址
61 #[inline(always)]
62 pub fn virt_2_phys(addr: usize) -> usize {
63     addr - PAGE_OFFSET as usize
64 }
65 
66 /// @brief 将物理地址转换为内核空间的虚拟地址
67 #[inline(always)]
68 pub fn phys_2_virt(addr: usize) -> usize {
69     addr + PAGE_OFFSET as usize
70 }
71 
72 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
73 pub enum PageTableKind {
74     /// 用户可访问的页表
75     User,
76     /// 内核页表
77     Kernel,
78     /// 内存虚拟化中使用的EPT
79     EPT,
80 }
81 
82 /// 物理内存地址
83 #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)]
84 #[repr(transparent)]
85 pub struct PhysAddr(usize);
86 
87 impl PhysAddr {
88     #[inline(always)]
89     pub const fn new(address: usize) -> Self {
90         Self(address)
91     }
92 
93     /// @brief 获取物理地址的值
94     #[inline(always)]
95     pub fn data(&self) -> usize {
96         self.0
97     }
98 
99     /// @brief 将物理地址加上一个偏移量
100     #[inline(always)]
101     pub fn add(self, offset: usize) -> Self {
102         Self(self.0 + offset)
103     }
104 
105     /// @brief 判断物理地址是否按照指定要求对齐
106     #[inline(always)]
107     pub fn check_aligned(&self, align: usize) -> bool {
108         return self.0 & (align - 1) == 0;
109     }
110 
111     #[inline(always)]
112     pub fn is_null(&self) -> bool {
113         return self.0 == 0;
114     }
115 }
116 
117 impl Debug for PhysAddr {
118     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119         write!(f, "PhysAddr({:#x})", self.0)
120     }
121 }
122 
123 impl core::ops::Add<usize> for PhysAddr {
124     type Output = Self;
125 
126     #[inline(always)]
127     fn add(self, rhs: usize) -> Self::Output {
128         return Self(self.0 + rhs);
129     }
130 }
131 
132 impl core::ops::AddAssign<usize> for PhysAddr {
133     #[inline(always)]
134     fn add_assign(&mut self, rhs: usize) {
135         self.0 += rhs;
136     }
137 }
138 
139 impl core::ops::Add<PhysAddr> for PhysAddr {
140     type Output = Self;
141 
142     #[inline(always)]
143     fn add(self, rhs: PhysAddr) -> Self::Output {
144         return Self(self.0 + rhs.0);
145     }
146 }
147 
148 impl core::ops::AddAssign<PhysAddr> for PhysAddr {
149     #[inline(always)]
150     fn add_assign(&mut self, rhs: PhysAddr) {
151         self.0 += rhs.0;
152     }
153 }
154 
155 impl core::ops::BitOrAssign<usize> for PhysAddr {
156     #[inline(always)]
157     fn bitor_assign(&mut self, rhs: usize) {
158         self.0 |= rhs;
159     }
160 }
161 
162 impl core::ops::BitOrAssign<PhysAddr> for PhysAddr {
163     #[inline(always)]
164     fn bitor_assign(&mut self, rhs: PhysAddr) {
165         self.0 |= rhs.0;
166     }
167 }
168 
169 impl core::ops::Sub<usize> for PhysAddr {
170     type Output = Self;
171 
172     #[inline(always)]
173     fn sub(self, rhs: usize) -> Self::Output {
174         return Self(self.0 - rhs);
175     }
176 }
177 
178 impl core::ops::SubAssign<usize> for PhysAddr {
179     #[inline(always)]
180     fn sub_assign(&mut self, rhs: usize) {
181         self.0 -= rhs;
182     }
183 }
184 
185 impl core::ops::Sub<PhysAddr> for PhysAddr {
186     type Output = usize;
187 
188     #[inline(always)]
189     fn sub(self, rhs: PhysAddr) -> Self::Output {
190         return self.0 - rhs.0;
191     }
192 }
193 
194 impl core::ops::SubAssign<PhysAddr> for PhysAddr {
195     #[inline(always)]
196     fn sub_assign(&mut self, rhs: PhysAddr) {
197         self.0 -= rhs.0;
198     }
199 }
200 
201 /// 虚拟内存地址
202 #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)]
203 #[repr(transparent)]
204 pub struct VirtAddr(usize);
205 
206 impl VirtAddr {
207     #[inline(always)]
208     pub const fn new(address: usize) -> Self {
209         return Self(address);
210     }
211 
212     /// @brief 获取虚拟地址的值
213     #[inline(always)]
214     pub fn data(&self) -> usize {
215         return self.0;
216     }
217 
218     /// @brief 判断虚拟地址的类型
219     #[inline(always)]
220     pub fn kind(&self) -> PageTableKind {
221         if self.check_user() {
222             return PageTableKind::User;
223         } else {
224             return PageTableKind::Kernel;
225         }
226     }
227 
228     /// @brief 判断虚拟地址是否按照指定要求对齐
229     #[inline(always)]
230     pub fn check_aligned(&self, align: usize) -> bool {
231         return self.0 & (align - 1) == 0;
232     }
233 
234     /// @brief 判断虚拟地址是否在用户空间
235     #[inline(always)]
236     pub fn check_user(&self) -> bool {
237         if self < &MMArch::USER_END_VADDR {
238             return true;
239         } else {
240             return false;
241         }
242     }
243 
244     #[inline(always)]
245     pub fn as_ptr<T>(self) -> *mut T {
246         return self.0 as *mut T;
247     }
248 
249     #[inline(always)]
250     pub fn is_null(&self) -> bool {
251         return self.0 == 0;
252     }
253 }
254 
255 impl Add<VirtAddr> for VirtAddr {
256     type Output = Self;
257 
258     #[inline(always)]
259     fn add(self, rhs: VirtAddr) -> Self::Output {
260         return Self(self.0 + rhs.0);
261     }
262 }
263 
264 impl Add<usize> for VirtAddr {
265     type Output = Self;
266 
267     #[inline(always)]
268     fn add(self, rhs: usize) -> Self::Output {
269         return Self(self.0 + rhs);
270     }
271 }
272 
273 impl Sub<VirtAddr> for VirtAddr {
274     type Output = usize;
275 
276     #[inline(always)]
277     fn sub(self, rhs: VirtAddr) -> Self::Output {
278         return self.0 - rhs.0;
279     }
280 }
281 
282 impl Sub<usize> for VirtAddr {
283     type Output = Self;
284 
285     #[inline(always)]
286     fn sub(self, rhs: usize) -> Self::Output {
287         return Self(self.0 - rhs);
288     }
289 }
290 
291 impl AddAssign<usize> for VirtAddr {
292     #[inline(always)]
293     fn add_assign(&mut self, rhs: usize) {
294         self.0 += rhs;
295     }
296 }
297 
298 impl AddAssign<VirtAddr> for VirtAddr {
299     #[inline(always)]
300     fn add_assign(&mut self, rhs: VirtAddr) {
301         self.0 += rhs.0;
302     }
303 }
304 
305 impl SubAssign<usize> for VirtAddr {
306     #[inline(always)]
307     fn sub_assign(&mut self, rhs: usize) {
308         self.0 -= rhs;
309     }
310 }
311 
312 impl SubAssign<VirtAddr> for VirtAddr {
313     #[inline(always)]
314     fn sub_assign(&mut self, rhs: VirtAddr) {
315         self.0 -= rhs.0;
316     }
317 }
318 
319 impl Debug for VirtAddr {
320     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
321         write!(f, "VirtAddr({:#x})", self.0)
322     }
323 }
324 
325 /// @brief 物理内存区域
326 #[derive(Clone, Copy, Debug)]
327 pub struct PhysMemoryArea {
328     /// 物理基地址
329     pub base: PhysAddr,
330     /// 该区域的物理内存大小
331     pub size: usize,
332 }
333 
334 impl PhysMemoryArea {
335     pub const DEFAULT: Self = Self {
336         base: PhysAddr::new(0),
337         size: 0,
338     };
339 
340     pub fn new(base: PhysAddr, size: usize) -> Self {
341         Self { base, size }
342     }
343 
344     /// 返回向上页面对齐的区域起始物理地址
345     pub fn area_base_aligned(&self) -> PhysAddr {
346         return PhysAddr::new(
347             (self.base.data() + (MMArch::PAGE_SIZE - 1)) & !(MMArch::PAGE_SIZE - 1),
348         );
349     }
350 
351     /// 返回向下页面对齐的区域截止物理地址
352     pub fn area_end_aligned(&self) -> PhysAddr {
353         return PhysAddr::new((self.base.data() + self.size) & !(MMArch::PAGE_SIZE - 1));
354     }
355 }
356 
357 impl Default for PhysMemoryArea {
358     fn default() -> Self {
359         return Self::DEFAULT;
360     }
361 }
362 
363 pub trait MemoryManagementArch: Clone + Copy + Debug {
364     /// 页面大小的shift(假如页面4K,那么这个值就是12,因为2^12=4096)
365     const PAGE_SHIFT: usize;
366     /// 每个页表的页表项数目。(以2^n次幂来表示)假如有512个页表项,那么这个值就是9
367     const PAGE_ENTRY_SHIFT: usize;
368     /// 页表层级数量
369     const PAGE_LEVELS: usize;
370 
371     /// 页表项的有效位的index(假如页表项的第0-51位有效,那么这个值就是52)
372     const ENTRY_ADDRESS_SHIFT: usize;
373     /// 页面的页表项的默认值
374     const ENTRY_FLAG_DEFAULT_PAGE: usize;
375     /// 页表的页表项的默认值
376     const ENTRY_FLAG_DEFAULT_TABLE: usize;
377     /// 页表项的present位被置位之后的值
378     const ENTRY_FLAG_PRESENT: usize;
379     /// 页表项为read only时的值
380     const ENTRY_FLAG_READONLY: usize;
381     /// 页表项为可读写状态的值
382     const ENTRY_FLAG_READWRITE: usize;
383     /// 页面项标记页面为user page的值
384     const ENTRY_FLAG_USER: usize;
385     /// 页面项标记页面为write through的值
386     const ENTRY_FLAG_WRITE_THROUGH: usize;
387     /// 页面项标记页面为cache disable的值
388     const ENTRY_FLAG_CACHE_DISABLE: usize;
389     /// 标记当前页面不可执行的标志位(Execute disable)(也就是说,不能从这段内存里面获取处理器指令)
390     const ENTRY_FLAG_NO_EXEC: usize;
391     /// 标记当前页面可执行的标志位(Execute enable)
392     const ENTRY_FLAG_EXEC: usize;
393 
394     /// 虚拟地址与物理地址的偏移量
395     const PHYS_OFFSET: usize;
396 
397     /// 每个页面的大小
398     const PAGE_SIZE: usize = 1 << Self::PAGE_SHIFT;
399     /// 通过这个mask,获取地址的页内偏移量
400     const PAGE_OFFSET_MASK: usize = Self::PAGE_SIZE - 1;
401     /// 通过这个mask,获取页的首地址
402     const PAGE_MASK: usize = !(Self::PAGE_OFFSET_MASK);
403     /// 页表项的地址、数据部分的shift。
404     /// 打个比方,如果这个值为52,那么意味着页表项的[0, 52)位,用于表示地址以及其他的标志位
405     const PAGE_ADDRESS_SHIFT: usize = Self::PAGE_LEVELS * Self::PAGE_ENTRY_SHIFT + Self::PAGE_SHIFT;
406     /// 最大的虚拟地址(对于不同的架构,由于上述PAGE_ADDRESS_SHIFT可能包括了reserved bits, 事实上能表示的虚拟地址应该比这个值要小)
407     const PAGE_ADDRESS_SIZE: usize = 1 << Self::PAGE_ADDRESS_SHIFT;
408     /// 页表项的值与这个常量进行与运算,得到的结果是所填写的物理地址
409     const PAGE_ADDRESS_MASK: usize = Self::PAGE_ADDRESS_SIZE - Self::PAGE_SIZE;
410     /// 每个页表项的大小
411     const PAGE_ENTRY_SIZE: usize = 1 << (Self::PAGE_SHIFT - Self::PAGE_ENTRY_SHIFT);
412     /// 每个页表的页表项数目
413     const PAGE_ENTRY_NUM: usize = 1 << Self::PAGE_ENTRY_SHIFT;
414     /// 该字段用于根据虚拟地址,获取该虚拟地址在对应的页表中是第几个页表项
415     const PAGE_ENTRY_MASK: usize = Self::PAGE_ENTRY_NUM - 1;
416 
417     const PAGE_NEGATIVE_MASK: usize = !((Self::PAGE_ADDRESS_SIZE) - 1);
418 
419     const ENTRY_ADDRESS_SIZE: usize = 1 << Self::ENTRY_ADDRESS_SHIFT;
420     /// 该mask用于获取页表项中地址字段
421     const ENTRY_ADDRESS_MASK: usize = Self::ENTRY_ADDRESS_SIZE - Self::PAGE_SIZE;
422     /// 这个mask用于获取页表项中的flags
423     const ENTRY_FLAGS_MASK: usize = !Self::ENTRY_ADDRESS_MASK;
424 
425     /// 用户空间的最高地址
426     const USER_END_VADDR: VirtAddr;
427     /// 用户堆的起始地址
428     const USER_BRK_START: VirtAddr;
429     /// 用户栈起始地址(向下生长,不包含该值)
430     const USER_STACK_START: VirtAddr;
431 
432     /// @brief 用于初始化内存管理模块与架构相关的信息。
433     /// 该函数应调用其他模块的接口,把可用内存区域添加到memblock,提供给BumpAllocator使用
434     unsafe fn init();
435 
436     /// @brief 读取指定虚拟地址的值,并假设它是类型T的指针
437     #[inline(always)]
438     unsafe fn read<T>(address: VirtAddr) -> T {
439         return ptr::read(address.data() as *const T);
440     }
441 
442     /// @brief 将value写入到指定的虚拟地址
443     #[inline(always)]
444     unsafe fn write<T>(address: VirtAddr, value: T) {
445         ptr::write(address.data() as *mut T, value);
446     }
447 
448     #[inline(always)]
449     unsafe fn write_bytes(address: VirtAddr, value: u8, count: usize) {
450         ptr::write_bytes(address.data() as *mut u8, value, count);
451     }
452 
453     /// @brief 刷新TLB中,关于指定虚拟地址的条目
454     unsafe fn invalidate_page(address: VirtAddr);
455 
456     /// @brief 刷新TLB中,所有的条目
457     unsafe fn invalidate_all();
458 
459     /// @brief 获取顶级页表的物理地址
460     unsafe fn table(table_kind: PageTableKind) -> PhysAddr;
461 
462     /// @brief 设置顶级页表的物理地址到处理器中
463     unsafe fn set_table(table_kind: PageTableKind, table: PhysAddr);
464 
465     /// @brief 将物理地址转换为虚拟地址.
466     ///
467     /// @param phys 物理地址
468     ///
469     /// @return 转换后的虚拟地址。如果转换失败,返回None
470     #[inline(always)]
471     unsafe fn phys_2_virt(phys: PhysAddr) -> Option<VirtAddr> {
472         if let Some(vaddr) = phys.data().checked_add(Self::PHYS_OFFSET) {
473             return Some(VirtAddr::new(vaddr));
474         } else {
475             return None;
476         }
477     }
478 
479     /// 将虚拟地址转换为物理地址
480     ///
481     /// ## 参数
482     ///
483     /// - `virt` 虚拟地址
484     ///
485     /// ## 返回值
486     ///
487     /// 转换后的物理地址。如果转换失败,返回None
488     #[inline(always)]
489     unsafe fn virt_2_phys(virt: VirtAddr) -> Option<PhysAddr> {
490         if let Some(paddr) = virt.data().checked_sub(Self::PHYS_OFFSET) {
491             return Some(PhysAddr::new(paddr));
492         } else {
493             return None;
494         }
495     }
496 
497     /// @brief 判断指定的虚拟地址是否正确(符合规范)
498     fn virt_is_valid(virt: VirtAddr) -> bool;
499 
500     /// 获取内存管理初始化时,创建的第一个内核页表的地址
501     fn initial_page_table() -> PhysAddr;
502 
503     /// 初始化新的usermapper,为用户进程创建页表
504     fn setup_new_usermapper() -> Result<UserMapper, SystemError>;
505 }
506 
507 /// @brief 虚拟地址范围
508 /// 该结构体用于表示一个虚拟地址范围,包括起始地址与大小
509 ///
510 /// 请注意与VMA进行区分,该结构体被VMA所包含
511 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
512 pub struct VirtRegion {
513     start: VirtAddr,
514     size: usize,
515 }
516 
517 #[allow(dead_code)]
518 impl VirtRegion {
519     /// # 创建一个新的虚拟地址范围
520     pub fn new(start: VirtAddr, size: usize) -> Self {
521         VirtRegion { start, size }
522     }
523 
524     /// 获取虚拟地址范围的起始地址
525     #[inline(always)]
526     pub fn start(&self) -> VirtAddr {
527         self.start
528     }
529 
530     /// 获取虚拟地址范围的截止地址(不包括返回的地址)
531     #[inline(always)]
532     pub fn end(&self) -> VirtAddr {
533         return self.start().add(self.size);
534     }
535 
536     /// # Create a new VirtRegion from a range [start, end)
537     ///
538     /// If end <= start, return None
539     pub fn between(start: VirtAddr, end: VirtAddr) -> Option<Self> {
540         if unlikely(end.data() <= start.data()) {
541             return None;
542         }
543         let size = end.data() - start.data();
544         return Some(VirtRegion::new(start, size));
545     }
546 
547     /// # 取两个虚拟地址范围的交集
548     ///
549     /// 如果两个虚拟地址范围没有交集,返回None
550     pub fn intersect(&self, other: &VirtRegion) -> Option<VirtRegion> {
551         let start = self.start.max(other.start);
552         let end = self.end().min(other.end());
553         return VirtRegion::between(start, end);
554     }
555 
556     /// 设置虚拟地址范围的起始地址
557     #[inline(always)]
558     pub fn set_start(&mut self, start: VirtAddr) {
559         self.start = start;
560     }
561 
562     #[inline(always)]
563     pub fn size(&self) -> usize {
564         self.size
565     }
566 
567     /// 设置虚拟地址范围的大小
568     #[inline(always)]
569     pub fn set_size(&mut self, size: usize) {
570         self.size = size;
571     }
572 
573     /// 判断虚拟地址范围是否为空
574     #[inline(always)]
575     pub fn is_empty(&self) -> bool {
576         self.size == 0
577     }
578 
579     /// 将虚拟地址区域的大小向上对齐到页大小
580     #[inline(always)]
581     pub fn round_up_size_to_page(self) -> Self {
582         return VirtRegion::new(self.start, round_up_to_page_size(self.size));
583     }
584 
585     /// 判断两个虚拟地址范围是否由于具有交集而导致冲突
586     #[inline(always)]
587     pub fn collide(&self, other: &VirtRegion) -> bool {
588         return self.intersect(other).is_some();
589     }
590 
591     pub fn iter_pages(&self) -> VirtPageFrameIter {
592         return VirtPageFrame::iter_range(
593             VirtPageFrame::new(self.start),
594             VirtPageFrame::new(self.end()),
595         );
596     }
597 
598     /// 获取[self.start(), region.start())的虚拟地址范围
599     ///
600     /// 如果self.start() >= region.start(),返回None
601     pub fn before(self, region: &VirtRegion) -> Option<Self> {
602         return Self::between(self.start(), region.start());
603     }
604 
605     /// 获取[region.end(),self.end())的虚拟地址范围
606     ///
607     /// 如果 self.end() >= region.end() ,返回None
608     pub fn after(self, region: &VirtRegion) -> Option<Self> {
609         // if self.end() > region.end() none
610         return Self::between(region.end(), self.end());
611     }
612 
613     /// 把当前虚拟地址范围内的某个虚拟地址,转换为另一个虚拟地址范围内的虚拟地址
614     ///
615     /// 如果vaddr不在当前虚拟地址范围内,返回None
616     ///
617     /// 如果vaddr在当前虚拟地址范围内,返回vaddr在new_base中的虚拟地址
618     pub fn rebase(self, vaddr: VirtAddr, new_base: &VirtRegion) -> Option<VirtAddr> {
619         if !self.contains(vaddr) {
620             return None;
621         }
622         let offset = vaddr.data() - self.start().data();
623         let new_start = new_base.start().data() + offset;
624         return Some(VirtAddr::new(new_start));
625     }
626 
627     /// 判断虚拟地址范围是否包含指定的虚拟地址
628     pub fn contains(&self, addr: VirtAddr) -> bool {
629         return self.start() <= addr && addr < self.end();
630     }
631 
632     /// 创建当前虚拟地址范围的页面迭代器
633     pub fn pages(&self) -> VirtPageFrameIter {
634         return VirtPageFrame::iter_range(
635             VirtPageFrame::new(self.start()),
636             VirtPageFrame::new(self.end()),
637         );
638     }
639 }
640 
641 impl PartialOrd for VirtRegion {
642     fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
643         return self.start.partial_cmp(&other.start);
644     }
645 }
646 
647 impl Ord for VirtRegion {
648     fn cmp(&self, other: &Self) -> cmp::Ordering {
649         return self.start.cmp(&other.start);
650     }
651 }
652 
653 /// ## 判断虚拟地址是否超出了用户空间
654 ///
655 /// 如果虚拟地址超出了用户空间,返回Err(SystemError::EFAULT).
656 /// 如果end < start,返回Err(SystemError::EOVERFLOW)
657 ///
658 /// 否则返回Ok(())
659 pub fn verify_area(addr: VirtAddr, size: usize) -> Result<(), SystemError> {
660     let end = addr.add(size);
661     if unlikely(end.data() < addr.data()) {
662         return Err(SystemError::EOVERFLOW);
663     }
664 
665     if !addr.check_user() || !end.check_user() {
666         return Err(SystemError::EFAULT);
667     }
668 
669     return Ok(());
670 }
671