1 /// 引入Module 2 use crate::driver::{ 3 base::{ 4 device::{ 5 device_number::{DeviceNumber, Major}, 6 Device, DeviceError, IdTable, BLOCKDEVS, 7 }, 8 map::{ 9 DeviceStruct, DEV_MAJOR_DYN_END, DEV_MAJOR_DYN_EXT_END, DEV_MAJOR_DYN_EXT_START, 10 DEV_MAJOR_HASH_SIZE, DEV_MAJOR_MAX, 11 }, 12 }, 13 block::cache::{cached_block_device::BlockCache, BlockCacheError, BLOCK_SIZE}, 14 }; 15 16 use alloc::{string::String, sync::Arc, vec::Vec}; 17 use core::{any::Any, fmt::Display, ops::Deref}; 18 use log::error; 19 use system_error::SystemError; 20 21 use super::{disk_info::Partition, gendisk::GenDisk, manager::BlockDevMeta}; 22 23 /// 该文件定义了 Device 和 BlockDevice 的接口 24 /// Notice 设备错误码使用 Posix 规定的 int32_t 的错误码表示,而不是自己定义错误enum 25 26 // 使用方法: 27 // 假设 blk_dev 是块设备 28 // <blk_dev as Device>::read_at() 调用的是Device的函数 29 // <blk_dev as BlockDevice>::read_at() 调用的是BlockDevice的函数 30 31 /// 定义类型 32 pub type BlockId = usize; 33 34 /// 定义常量 35 pub const BLK_SIZE_LOG2_LIMIT: u8 = 12; // 设定块设备的块大小不能超过 1 << 12. 36 /// 在DragonOS中,我们认为磁盘的每个LBA大小均为512字节。(注意,文件系统的1个扇区可能事实上是多个LBA) 37 pub const LBA_SIZE: usize = 512; 38 39 #[derive(Debug, Clone, Copy)] 40 pub struct GeneralBlockRange { 41 pub lba_start: usize, 42 pub lba_end: usize, 43 } 44 45 impl GeneralBlockRange { 46 pub fn new(lba_start: usize, lba_end: usize) -> Option<Self> { 47 if lba_start >= lba_end { 48 return None; 49 } 50 return Some(GeneralBlockRange { lba_start, lba_end }); 51 } 52 53 #[inline] 54 pub fn len(&self) -> usize { 55 return self.lba_end - self.lba_start; 56 } 57 58 /// 取交集 59 pub fn intersects_with(&self, rhs: &Self) -> Option<Self> { 60 // 检查是否相交 61 if self.lba_start <= rhs.lba_end && self.lba_end >= rhs.lba_start { 62 // 计算相交部分的起始和结束 LBA 63 let start = usize::max(self.lba_start, rhs.lba_start); 64 let end = usize::min(self.lba_end, rhs.lba_end); 65 // 返回相交部分 66 GeneralBlockRange::new(start, end) 67 } else { 68 // 不相交,返回 None 69 None 70 } 71 } 72 } 73 74 /// @brief 块设备的迭代器 75 /// @usage 某次操作读/写块设备的[L,R]范围内的字节, 76 /// 那么可以使用此结构体进行迭代遍历,每次调用next()返回一个BlockRange 77 pub struct BlockIter { 78 pub begin: usize, // 迭代器的起始位置 -> 块设备的地址 (单位是字节) 79 pub end: usize, 80 pub blk_size_log2: u8, 81 pub multiblock: bool, // 是否启用连续整块同时遍历 82 } 83 84 /// @brief Range搭配迭代器BlockIter使用,[L,R]区间被分割成多个小的Range 85 /// Range要么是整块,要么是一块的某一部分 86 /// 细节: range = [begin, end) 左闭右开 87 pub struct BlockRange { 88 pub lba_start: usize, // 起始块的lba_id 89 pub lba_end: usize, // 终止块的lba_id 90 pub begin: usize, // 起始位置在块内的偏移量, 如果BlockIter启用Multiblock,则是多个块的偏移量 91 pub end: usize, // 结束位置在块内的偏移量,单位是字节 92 pub blk_size_log2: u8, 93 } 94 95 impl BlockIter { 96 #[allow(dead_code)] 97 pub fn new(start_addr: usize, end_addr: usize, blk_size_log2: u8) -> BlockIter { 98 return BlockIter { 99 begin: start_addr, 100 end: end_addr, 101 blk_size_log2, 102 multiblock: false, 103 }; 104 } 105 pub fn new_multiblock(start_addr: usize, end_addr: usize, blk_size_log2: u8) -> BlockIter { 106 return BlockIter { 107 begin: start_addr, 108 end: end_addr, 109 110 blk_size_log2, 111 multiblock: true, 112 }; 113 } 114 115 /// 获取下一个整块或者不完整的块 116 pub fn next_block(&mut self) -> BlockRange { 117 let blk_size_log2 = self.blk_size_log2; 118 let blk_size = 1usize << self.blk_size_log2; 119 let lba_id = self.begin / blk_size; 120 let begin = self.begin % blk_size; 121 let end = if lba_id == self.end / blk_size { 122 self.end % blk_size 123 } else { 124 blk_size 125 }; 126 127 self.begin += end - begin; 128 129 return BlockRange { 130 lba_start: lba_id, 131 lba_end: lba_id + 1, 132 begin, 133 end, 134 blk_size_log2, 135 }; 136 } 137 138 /// 如果能返回多个连续的整块,则返回;否则调用next_block()返回不完整的块 139 pub fn next_multiblock(&mut self) -> BlockRange { 140 let blk_size_log2 = self.blk_size_log2; 141 let blk_size = 1usize << self.blk_size_log2; 142 let lba_start = self.begin / blk_size; 143 let lba_end = self.end / blk_size; 144 145 // 如果不是整块,先返回非整块的小部分 146 if __bytes_to_lba(self.begin, blk_size) 147 != __bytes_to_lba(self.begin + blk_size - 1, blk_size) 148 || lba_start == lba_end 149 { 150 return self.next_block(); 151 } 152 153 let begin = self.begin % blk_size; // 因为是多个整块,这里必然是0 154 let end = __lba_to_bytes(lba_end, blk_size) - self.begin; 155 156 self.begin += end - begin; 157 158 return BlockRange { 159 lba_start, 160 lba_end, 161 begin, 162 end, 163 blk_size_log2, 164 }; 165 } 166 } 167 168 /// BlockIter 函数实现 169 impl Iterator for BlockIter { 170 type Item = BlockRange; 171 172 fn next(&mut self) -> Option<<Self as Iterator>::Item> { 173 if self.begin >= self.end { 174 return None; 175 } 176 if self.multiblock { 177 return Some(self.next_multiblock()); 178 } else { 179 return Some(self.next_block()); 180 } 181 } 182 } 183 184 /// BlockRange 函数实现 185 impl BlockRange { 186 #[allow(dead_code)] 187 pub fn is_empty(&self) -> bool { 188 return self.end == self.begin; 189 } 190 pub fn len(&self) -> usize { 191 return self.end - self.begin; 192 } 193 /// 判断是不是整块 194 pub fn is_full(&self) -> bool { 195 return self.len() == (1usize << self.blk_size_log2); 196 } 197 /// 判断是不是多个整块连在一起 198 pub fn is_multi(&self) -> bool { 199 return self.len() >= (1usize << self.blk_size_log2) 200 && (self.len() % (1usize << self.blk_size_log2) == 0); 201 } 202 /// 获取 BlockRange 在块设备内部的起始位置 (单位是字节) 203 pub fn origin_begin(&self) -> usize { 204 return (self.lba_start << self.blk_size_log2) + self.begin; 205 } 206 /// 获取 BlockRange 在块设备内部的结尾位置 (单位是字节) 207 pub fn origin_end(&self) -> usize { 208 return (self.lba_start << self.blk_size_log2) + self.end; 209 } 210 } 211 212 /// 从字节地址转换到lba id 213 #[inline] 214 pub fn __bytes_to_lba(addr: usize, blk_size: usize) -> BlockId { 215 return addr / blk_size; 216 } 217 218 /// 从lba id转换到字节地址, 返回lba_id的最左侧字节 219 #[inline] 220 pub fn __lba_to_bytes(lba_id: usize, blk_size: usize) -> BlockId { 221 return lba_id * blk_size; 222 } 223 224 /// 块设备的名字 225 pub struct BlockDevName { 226 name: Arc<String>, 227 id: usize, 228 } 229 230 impl BlockDevName { 231 pub fn new(name: String, id: usize) -> Self { 232 return BlockDevName { 233 name: Arc::new(name), 234 id, 235 }; 236 } 237 238 #[inline] 239 pub fn id(&self) -> usize { 240 return self.id; 241 } 242 } 243 244 impl core::fmt::Debug for BlockDevName { 245 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 246 return write!(f, "{}", self.name); 247 } 248 } 249 250 impl Display for BlockDevName { 251 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 252 return write!(f, "{}", self.name); 253 } 254 } 255 256 impl Clone for BlockDevName { 257 fn clone(&self) -> Self { 258 return BlockDevName { 259 name: self.name.clone(), 260 id: self.id, 261 }; 262 } 263 } 264 265 impl core::hash::Hash for BlockDevName { 266 fn hash<H: core::hash::Hasher>(&self, state: &mut H) { 267 self.name.hash(state); 268 } 269 } 270 271 impl Deref for BlockDevName { 272 type Target = String; 273 274 fn deref(&self) -> &Self::Target { 275 return self.name.as_ref(); 276 } 277 } 278 279 impl PartialEq for BlockDevName { 280 fn eq(&self, other: &Self) -> bool { 281 return self.name == other.name; 282 } 283 } 284 285 impl Eq for BlockDevName {} 286 287 /// @brief 块设备应该实现的操作 288 pub trait BlockDevice: Device { 289 /// # dev_name 290 /// 返回块设备的名字 291 fn dev_name(&self) -> &BlockDevName; 292 293 fn blkdev_meta(&self) -> &BlockDevMeta; 294 295 /// 获取设备的扇区范围 296 fn disk_range(&self) -> GeneralBlockRange; 297 298 /// @brief: 在块设备中,从第lba_id_start个块开始,读取count个块数据,存放到buf中 299 /// 300 /// @parameter lba_id_start: 起始块 301 /// @parameter count: 读取块的数量 302 /// @parameter buf: 目标数组 303 /// @return: 如果操作成功,返回 Ok(操作的长度) 其中单位是字节; 304 /// 否则返回Err(错误码),其中错误码为负数; 305 /// 如果操作异常,但是并没有检查出什么错误,将返回Err(已操作的长度) 306 fn read_at_sync( 307 &self, 308 lba_id_start: BlockId, 309 count: usize, 310 buf: &mut [u8], 311 ) -> Result<usize, SystemError>; 312 313 /// @brief: 在块设备中,从第lba_id_start个块开始,把buf中的count个块数据,存放到设备中 314 /// @parameter lba_id_start: 起始块 315 /// @parameter count: 写入块的数量 316 /// @parameter buf: 目标数组 317 /// @return: 如果操作成功,返回 Ok(操作的长度) 其中单位是字节; 318 /// 否则返回Err(错误码),其中错误码为负数; 319 /// 如果操作异常,但是并没有检查出什么错误,将返回Err(已操作的长度) 320 fn write_at_sync( 321 &self, 322 lba_id_start: BlockId, 323 count: usize, 324 buf: &[u8], 325 ) -> Result<usize, SystemError>; 326 327 /// @brief: 同步磁盘信息,把所有的dirty数据写回硬盘 - 待实现 328 fn sync(&self) -> Result<(), SystemError>; 329 330 /// @brief: 每个块设备都必须固定自己块大小,而且该块大小必须是2的幂次 331 /// @return: 返回一个固定量,硬编码(编程的时候固定的常量). 332 fn blk_size_log2(&self) -> u8; 333 334 // TODO: 待实现 open, close 335 336 /// @brief 本函数用于实现动态转换。 337 /// 具体的文件系统在实现本函数时,最简单的方式就是:直接返回self 338 fn as_any_ref(&self) -> &dyn Any; 339 340 /// @brief 本函数用于将BlockDevice转换为Device。 341 /// 由于实现了BlockDevice的结构体,本身也实现了Device Trait, 因此转换是可能的。 342 /// 思路:在BlockDevice的结构体中新增一个self_ref变量,返回self_ref.upgrade()即可。 343 fn device(&self) -> Arc<dyn Device>; 344 345 /// @brief 返回块设备的块大小(单位:字节) 346 fn block_size(&self) -> usize; 347 348 /// @brief 返回当前磁盘上的所有分区的Arc指针数组 349 fn partitions(&self) -> Vec<Arc<Partition>>; 350 351 /// # 函数的功能 352 /// 经由Cache对块设备的读操作 353 fn read_at( 354 &self, 355 lba_id_start: BlockId, 356 count: usize, 357 buf: &mut [u8], 358 ) -> Result<usize, SystemError> { 359 self.cache_read(lba_id_start, count, buf) 360 } 361 362 /// # 函数的功能 363 /// 经由Cache对块设备的写操作 364 fn write_at( 365 &self, 366 lba_id_start: BlockId, 367 count: usize, 368 buf: &[u8], 369 ) -> Result<usize, SystemError> { 370 self.cache_write(lba_id_start, count, buf) 371 } 372 373 /// # 函数的功能 374 /// 其功能对外而言和read_at函数完全一致,但是加入blockcache的功能 375 fn cache_read( 376 &self, 377 lba_id_start: BlockId, 378 count: usize, 379 buf: &mut [u8], 380 ) -> Result<usize, SystemError> { 381 let cache_response = BlockCache::read(lba_id_start, count, buf); 382 if let Err(e) = cache_response { 383 match e { 384 BlockCacheError::StaticParameterError => { 385 BlockCache::init(); 386 let ans = self.read_at_sync(lba_id_start, count, buf)?; 387 return Ok(ans); 388 } 389 BlockCacheError::BlockFaultError(fail_vec) => { 390 let ans = self.read_at_sync(lba_id_start, count, buf)?; 391 let _ = BlockCache::insert(fail_vec, buf); 392 return Ok(ans); 393 } 394 _ => { 395 let ans = self.read_at_sync(lba_id_start, count, buf)?; 396 return Ok(ans); 397 } 398 } 399 } else { 400 return Ok(count * BLOCK_SIZE); 401 } 402 } 403 404 /// # 函数功能 405 /// 其功能对外而言和write_at函数完全一致,但是加入blockcache的功能 406 fn cache_write( 407 &self, 408 lba_id_start: BlockId, 409 count: usize, 410 buf: &[u8], 411 ) -> Result<usize, SystemError> { 412 let _cache_response = BlockCache::immediate_write(lba_id_start, count, buf); 413 self.write_at_sync(lba_id_start, count, buf) 414 } 415 416 fn write_at_bytes(&self, offset: usize, len: usize, buf: &[u8]) -> Result<usize, SystemError> { 417 if len > buf.len() { 418 return Err(SystemError::E2BIG); 419 } 420 421 let iter = BlockIter::new_multiblock(offset, offset + len, self.blk_size_log2()); 422 let multi = iter.multiblock; 423 424 for range in iter { 425 let buf_begin = range.origin_begin() - offset; // 本次读操作的起始位置/已经读了这么多字节 426 let buf_end = range.origin_end() - offset; 427 let buf_slice = &buf[buf_begin..buf_end]; 428 let count: usize = range.lba_end - range.lba_start; 429 let full = multi && range.is_multi() || !multi && range.is_full(); 430 431 if full { 432 self.write_at(range.lba_start, count, buf_slice)?; 433 } else { 434 if self.blk_size_log2() > BLK_SIZE_LOG2_LIMIT { 435 return Err(SystemError::E2BIG); 436 } 437 438 let mut temp = vec![0; 1usize << self.blk_size_log2()]; 439 // 由于块设备每次读写都是整块的,在不完整写入之前,必须把不完整的地方补全 440 self.read_at(range.lba_start, 1, &mut temp[..])?; 441 // 把数据从临时buffer复制到目标buffer 442 temp[range.begin..range.end].copy_from_slice(buf_slice); 443 self.write_at(range.lba_start, 1, &temp[..])?; 444 } 445 } 446 return Ok(len); 447 } 448 449 fn read_at_bytes( 450 &self, 451 offset: usize, 452 len: usize, 453 buf: &mut [u8], 454 ) -> Result<usize, SystemError> { 455 if len > buf.len() { 456 return Err(SystemError::E2BIG); 457 } 458 459 let iter = BlockIter::new_multiblock(offset, offset + len, self.blk_size_log2()); 460 let multi = iter.multiblock; 461 462 // 枚举每一个range 463 for range in iter { 464 let buf_begin = range.origin_begin() - offset; // 本次读操作的起始位置/已经读了这么多字节 465 let buf_end = range.origin_end() - offset; 466 let buf_slice = &mut buf[buf_begin..buf_end]; 467 let count: usize = range.lba_end - range.lba_start; 468 let full = multi && range.is_multi() || !multi && range.is_full(); 469 470 // 读取整个block作为有效数据 471 if full { 472 // 调用 BlockDevice::read_at() 直接把引用传进去,不是把整个数组move进去 473 self.read_at(range.lba_start, count, buf_slice)?; 474 } else { 475 // 判断块的长度不能超过最大值 476 if self.blk_size_log2() > BLK_SIZE_LOG2_LIMIT { 477 return Err(SystemError::E2BIG); 478 } 479 480 let mut temp = vec![0; 1usize << self.blk_size_log2()]; 481 self.read_at(range.lba_start, 1, &mut temp[..])?; 482 483 // 把数据从临时buffer复制到目标buffer 484 buf_slice.copy_from_slice(&temp[range.begin..range.end]); 485 } 486 } 487 return Ok(len); 488 } 489 490 /// # gendisk注册成功的回调函数 491 fn callback_gendisk_registered(&self, _gendisk: &Arc<GenDisk>) -> Result<(), SystemError> { 492 Ok(()) 493 } 494 } 495 496 /// @brief 块设备框架函数集 497 pub struct BlockDeviceOps; 498 499 impl BlockDeviceOps { 500 /// @brief: 主设备号转下标 501 /// @parameter: major: 主设备号 502 /// @return: 返回下标 503 #[allow(dead_code)] 504 fn major_to_index(major: Major) -> usize { 505 return (major.data() % DEV_MAJOR_HASH_SIZE) as usize; 506 } 507 508 /// @brief: 动态获取主设备号 509 /// @parameter: None 510 /// @return: 如果成功,返回主设备号,否则,返回错误码 511 #[allow(dead_code)] 512 fn find_dynamic_major() -> Result<Major, SystemError> { 513 let blockdevs = BLOCKDEVS.lock(); 514 // 寻找主设备号为234~255的设备 515 for index in ((DEV_MAJOR_DYN_END.data())..DEV_MAJOR_HASH_SIZE).rev() { 516 if let Some(item) = blockdevs.get(index as usize) { 517 if item.is_empty() { 518 return Ok(Major::new(index)); // 返回可用的主设备号 519 } 520 } 521 } 522 // 寻找主设备号在384~511的设备 523 for index in 524 ((DEV_MAJOR_DYN_EXT_END.data() + 1)..(DEV_MAJOR_DYN_EXT_START.data() + 1)).rev() 525 { 526 if let Some(blockdevss) = blockdevs.get(Self::major_to_index(Major::new(index))) { 527 let mut flag = true; 528 for item in blockdevss { 529 if item.device_number().major() == Major::new(index) { 530 flag = false; 531 break; 532 } 533 } 534 if flag { 535 // 如果数组中不存在主设备号等于index的设备 536 return Ok(Major::new(index)); // 返回可用的主设备号 537 } 538 } 539 } 540 return Err(SystemError::EBUSY); 541 } 542 543 /// @brief: 注册设备号,该函数需要指定主设备号 544 /// @parameter: from: 主设备号 545 /// count: 次设备号数量 546 /// name: 字符设备名 547 /// @return: 如果注册成功,返回设备号,否则,返回错误码 548 #[allow(dead_code)] 549 pub fn register_blockdev_region( 550 from: DeviceNumber, 551 count: u32, 552 name: &'static str, 553 ) -> Result<DeviceNumber, SystemError> { 554 Self::__register_blockdev_region(from, count, name) 555 } 556 557 /// @brief: 注册设备号,该函数自动分配主设备号 558 /// @parameter: baseminor: 主设备号 559 /// count: 次设备号数量 560 /// name: 字符设备名 561 /// @return: 如果注册成功,返回,否则,返回false 562 #[allow(dead_code)] 563 pub fn alloc_blockdev_region( 564 baseminor: u32, 565 count: u32, 566 name: &'static str, 567 ) -> Result<DeviceNumber, SystemError> { 568 Self::__register_blockdev_region( 569 DeviceNumber::new(Major::UNNAMED_MAJOR, baseminor), 570 count, 571 name, 572 ) 573 } 574 575 /// @brief: 注册设备号 576 /// @parameter: device_number: 设备号,主设备号如果为0,则动态分配 577 /// minorct: 次设备号数量 578 /// name: 字符设备名 579 /// @return: 如果注册成功,返回设备号,否则,返回错误码 580 fn __register_blockdev_region( 581 device_number: DeviceNumber, 582 minorct: u32, 583 name: &'static str, 584 ) -> Result<DeviceNumber, SystemError> { 585 let mut major = device_number.major(); 586 let baseminor = device_number.minor(); 587 if major >= DEV_MAJOR_MAX { 588 error!( 589 "DEV {} major requested {:?} is greater than the maximum {}\n", 590 name, 591 major, 592 DEV_MAJOR_MAX.data() - 1 593 ); 594 } 595 if minorct > DeviceNumber::MINOR_MASK + 1 - baseminor { 596 error!("DEV {} minor range requested ({}-{}) is out of range of maximum range ({}-{}) for a single major\n", 597 name, baseminor, baseminor + minorct - 1, 0, DeviceNumber::MINOR_MASK); 598 } 599 let blockdev = DeviceStruct::new(DeviceNumber::new(major, baseminor), minorct, name); 600 if major == Major::UNNAMED_MAJOR { 601 // 如果主设备号为0,则自动分配主设备号 602 major = Self::find_dynamic_major().expect("Find synamic major error.\n"); 603 } 604 if let Some(items) = BLOCKDEVS.lock().get_mut(Self::major_to_index(major)) { 605 let mut insert_index: usize = 0; 606 for (index, item) in items.iter().enumerate() { 607 insert_index = index; 608 match item.device_number().major().cmp(&major) { 609 core::cmp::Ordering::Less => continue, 610 core::cmp::Ordering::Greater => { 611 break; // 大于则向后插入 612 } 613 core::cmp::Ordering::Equal => { 614 if item.device_number().minor() + item.minorct() <= baseminor { 615 continue; // 下一个主设备号大于或者次设备号大于被插入的次设备号最大值 616 } 617 if item.base_minor() >= baseminor + minorct { 618 break; // 在此处插入 619 } 620 return Err(SystemError::EBUSY); // 存在重合的次设备号 621 } 622 } 623 } 624 items.insert(insert_index, blockdev); 625 } 626 627 return Ok(DeviceNumber::new(major, baseminor)); 628 } 629 630 /// @brief: 注销设备号 631 /// @parameter: major: 主设备号,如果为0,动态分配 632 /// baseminor: 起始次设备号 633 /// minorct: 次设备号数量 634 /// @return: 如果注销成功,返回(),否则,返回错误码 635 fn __unregister_blockdev_region( 636 device_number: DeviceNumber, 637 minorct: u32, 638 ) -> Result<(), SystemError> { 639 if let Some(items) = BLOCKDEVS 640 .lock() 641 .get_mut(Self::major_to_index(device_number.major())) 642 { 643 for (index, item) in items.iter().enumerate() { 644 if item.device_number() == device_number && item.minorct() == minorct { 645 // 设备号和数量都相等 646 items.remove(index); 647 return Ok(()); 648 } 649 } 650 } 651 return Err(SystemError::EBUSY); 652 } 653 654 /// @brief: 块设备注册 655 /// @parameter: cdev: 字符设备实例 656 /// dev_t: 字符设备号 657 /// range: 次设备号范围 658 /// @return: none 659 #[allow(dead_code)] 660 pub fn bdev_add(_bdev: Arc<dyn BlockDevice>, id_table: IdTable) -> Result<(), DeviceError> { 661 if id_table.device_number().data() == 0 { 662 error!("Device number can't be 0!\n"); 663 } 664 todo!("bdev_add") 665 // return device_manager().add_device(bdev.id_table(), bdev.device()); 666 } 667 668 /// @brief: block设备注销 669 /// @parameter: dev_t: 字符设备号 670 /// range: 次设备号范围 671 /// @return: none 672 #[allow(dead_code)] 673 pub fn bdev_del(_devnum: DeviceNumber, _range: usize) { 674 unimplemented!(); 675 } 676 } 677