mirror of
				https://github.com/jhbruhn/moonboot.git
				synced 2025-10-31 19:36:01 +00:00 
			
		
		
		
	Refactored swap/scratch to state
This commit is contained in:
		
							parent
							
								
									01fe251f1f
								
							
						
					
					
						commit
						e2d1ce797b
					
				
					 7 changed files with 136 additions and 135 deletions
				
			
		|  | @ -1,8 +1,7 @@ | ||||||
| use crate::{ | use crate::{ | ||||||
|     hardware::processor::Processor, |     hardware::processor::Processor, | ||||||
|     hardware::{Bank, Config}, |     hardware::{Bank, Config}, | ||||||
|     state::{ExchangeProgress, ExchangeStep, State, Update, UpdateError}, |     state::{Exchange, ExchangeProgress, ExchangeStep, MemoryError, State, Update, UpdateError}, | ||||||
|     swap::{MemoryError, Swap}, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use embedded_storage::Storage; | use embedded_storage::Storage; | ||||||
|  | @ -15,47 +14,47 @@ use defmt::Format; | ||||||
| /// Use this from your bootloader application and call boot() to do the magic, reading the current
 | /// Use this from your bootloader application and call boot() to do the magic, reading the current
 | ||||||
| /// state via the State type and then jumping to the new image using the Jumper specified
 | /// state via the State type and then jumping to the new image using the Jumper specified
 | ||||||
| pub struct MoonbootBoot< | pub struct MoonbootBoot< | ||||||
|     InternalMemory: Storage, |     STORAGE: Storage, | ||||||
|     HardwareState: State, |     STATE: State, | ||||||
|     CPU: Processor, // TODO: Wrap these into a context struct like rubble?
 |     PROCESSOR: Processor, // TODO: Wrap these into a context struct like rubble?
 | ||||||
|     PageSwap: Swap, |     EXCHANGE: Exchange, | ||||||
|     const INTERNAL_PAGE_SIZE: usize, |     const INTERNAL_PAGE_SIZE: usize, | ||||||
| > { | > { | ||||||
|     config: Config, |     config: Config, | ||||||
|     internal_memory: InternalMemory, |     storage: STORAGE, | ||||||
|     state: HardwareState, |     state: STATE, | ||||||
|     processor: CPU, |     processor: PROCESSOR, | ||||||
|     swap: PageSwap, |     exchange: EXCHANGE, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl< | impl< | ||||||
|         InternalMemory: Storage, |         STORAGE: Storage, | ||||||
|         HardwareState: State, |         STATE: State, | ||||||
|         CPU: Processor, |         PROCESSOR: Processor, | ||||||
|         PageSwap: Swap, |         EXCHANGE: Exchange, | ||||||
|         const INTERNAL_PAGE_SIZE: usize, |         const INTERNAL_PAGE_SIZE: usize, | ||||||
|     > MoonbootBoot<InternalMemory, HardwareState, CPU, PageSwap, INTERNAL_PAGE_SIZE> |     > MoonbootBoot<STORAGE, STATE, PROCESSOR, EXCHANGE, INTERNAL_PAGE_SIZE> | ||||||
| { | { | ||||||
|     /// create a new instance of the bootloader
 |     /// create a new instance of the bootloader
 | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         config: Config, |         config: Config, | ||||||
|         internal_memory: InternalMemory, |         storage: STORAGE, | ||||||
|         state: HardwareState, |         state: STATE, | ||||||
|         processor: CPU, |         processor: PROCESSOR, | ||||||
|         swap: PageSwap, |         exchange: EXCHANGE, | ||||||
|     ) -> MoonbootBoot<InternalMemory, HardwareState, CPU, PageSwap, INTERNAL_PAGE_SIZE> { |     ) -> MoonbootBoot<STORAGE, STATE, PROCESSOR, EXCHANGE, INTERNAL_PAGE_SIZE> { | ||||||
|         Self { |         Self { | ||||||
|             config, |             config, | ||||||
|             internal_memory, |             storage, | ||||||
|             state, |             state, | ||||||
|             processor, |             processor, | ||||||
|             swap, |             exchange, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Destroy this instance of the bootloader and return access to the hardware peripheral
 |     /// Destroy this instance of the bootloader and return access to the hardware peripheral
 | ||||||
|     pub fn destroy(self) -> (InternalMemory, HardwareState, CPU) { |     pub fn destroy(self) -> (STORAGE, STATE, PROCESSOR) { | ||||||
|         (self.internal_memory, self.state, self.processor) |         (self.storage, self.state, self.processor) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Execute the update and boot logic of the bootloader
 |     /// Execute the update and boot logic of the bootloader
 | ||||||
|  | @ -124,9 +123,9 @@ impl< | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         let exchange_result = self |         let exchange_result = self | ||||||
|             .swap |             .exchange | ||||||
|             .exchange::<InternalMemory, HardwareState, INTERNAL_PAGE_SIZE>( |             .exchange::<STORAGE, STATE, INTERNAL_PAGE_SIZE>( | ||||||
|                 &mut self.internal_memory, |                 &mut self.storage, | ||||||
|                 &mut self.state, |                 &mut self.state, | ||||||
|                 progress, |                 progress, | ||||||
|             ); |             ); | ||||||
|  | @ -165,7 +164,7 @@ impl< | ||||||
|                 old.location, |                 old.location, | ||||||
|                 old.size / 1024, |                 old.size / 1024, | ||||||
|                 new.location, |                 new.location, | ||||||
|                 new.size / 1024, |                 new.size / 1024 | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             // Try to exchange the firmware images
 |             // Try to exchange the firmware images
 | ||||||
|  | @ -195,9 +194,9 @@ impl< | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn exchange_banks(&mut self, a: Bank, b: Bank) -> Result<(), MemoryError> { |     fn exchange_banks(&mut self, a: Bank, b: Bank) -> Result<(), MemoryError> { | ||||||
|         self.swap |         self.exchange | ||||||
|             .exchange::<InternalMemory, HardwareState, INTERNAL_PAGE_SIZE>( |             .exchange::<STORAGE, STATE, INTERNAL_PAGE_SIZE>( | ||||||
|                 &mut self.internal_memory, |                 &mut self.storage, | ||||||
|                 &mut self.state, |                 &mut self.state, | ||||||
|                 ExchangeProgress { |                 ExchangeProgress { | ||||||
|                     a, |                     a, | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ pub mod cortex_m { | ||||||
|         fn do_jump(&mut self, address: super::Address) -> ! { |         fn do_jump(&mut self, address: super::Address) -> ! { | ||||||
|             unsafe { |             unsafe { | ||||||
|                 // Set Vector Table to new vector table (unsafe but okay here)
 |                 // Set Vector Table to new vector table (unsafe but okay here)
 | ||||||
|                 (*cortex_m::peripheral::SCB::ptr()).vtor.write(address); |                 (*cortex_m::peripheral::SCB::PTR).vtor.write(address); | ||||||
| 
 | 
 | ||||||
|                 cortex_m::asm::bootload(address as *const u32); |                 cortex_m::asm::bootload(address as *const u32); | ||||||
|             } |             } | ||||||
|  | @ -40,4 +40,3 @@ pub mod cortex_m { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -24,8 +24,6 @@ pub use manager::MoonbootManager; | ||||||
| pub mod hardware; | pub mod hardware; | ||||||
| /// Shared state management between firmware and bootloader
 | /// Shared state management between firmware and bootloader
 | ||||||
| pub mod state; | pub mod state; | ||||||
| /// Abstractions on the method of flash page swapping.
 |  | ||||||
| pub mod swap; |  | ||||||
| 
 | 
 | ||||||
| pub use embedded_storage; | pub use embedded_storage; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,10 +1,37 @@ | ||||||
|  | #[cfg(feature = "ram-state")] | ||||||
|  | pub mod ram; | ||||||
|  | 
 | ||||||
|  | #[cfg(feature = "scratch-state")] | ||||||
|  | pub mod scratch; | ||||||
|  | 
 | ||||||
|  | use embedded_storage::Storage; | ||||||
|  | 
 | ||||||
|  | /// Error occured during memory access
 | ||||||
|  | #[cfg_attr(feature = "use-defmt", derive(Format))] | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum MemoryError { | ||||||
|  |     BankSizeNotEqual, | ||||||
|  |     BankSizeZero, | ||||||
|  |     ReadFailure, | ||||||
|  |     WriteFailure, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Abstraction for the exchange operation of the current state.
 | ||||||
|  | pub trait Exchange { | ||||||
|  |     fn exchange<STORAGE: Storage, STATE: State, const INTERNAL_PAGE_SIZE: usize>( | ||||||
|  |         &mut self, | ||||||
|  |         internal_memory: &mut STORAGE, | ||||||
|  |         state: &mut STATE, | ||||||
|  |         progress: ExchangeProgress, | ||||||
|  |     ) -> Result<(), MemoryError>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| use crate::hardware::Bank; | use crate::hardware::Bank; | ||||||
| use crate::log; |  | ||||||
| 
 | 
 | ||||||
| use crc::{Crc, CRC_32_CKSUM}; | use crc::{Crc, CRC_32_CKSUM}; | ||||||
| #[cfg(feature = "defmt")] | #[cfg(feature = "defmt")] | ||||||
| use defmt::Format; | use defmt::Format; | ||||||
| #[cfg(feature = "ram-state")] | #[cfg(any(feature = "ram-state", feature = "scratch-state"))] | ||||||
| use desse::{Desse, DesseSized}; | use desse::{Desse, DesseSized}; | ||||||
| #[cfg(feature = "serde")] | #[cfg(feature = "serde")] | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
|  | @ -14,7 +41,10 @@ use serde::{Deserialize, Serialize}; | ||||||
| // exchanged via software updates easily
 | // exchanged via software updates easily
 | ||||||
| #[cfg_attr(feature = "use-defmt", derive(Format))] | #[cfg_attr(feature = "use-defmt", derive(Format))] | ||||||
| #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | ||||||
| #[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] | #[cfg_attr(
 | ||||||
|  |     any(feature = "ram-state", feature = "scratch-state"), | ||||||
|  |     derive(Desse, DesseSized) | ||||||
|  | )] | ||||||
| #[derive(Debug, PartialEq, Eq)] | #[derive(Debug, PartialEq, Eq)] | ||||||
| pub enum Update { | pub enum Update { | ||||||
|     // No update requested, just jump to the application
 |     // No update requested, just jump to the application
 | ||||||
|  | @ -33,7 +63,10 @@ pub enum Update { | ||||||
| 
 | 
 | ||||||
| #[cfg_attr(feature = "use-defmt", derive(Format))] | #[cfg_attr(feature = "use-defmt", derive(Format))] | ||||||
| #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | ||||||
| #[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] | #[cfg_attr(
 | ||||||
|  |     any(feature = "ram-state", feature = "scratch-state"), | ||||||
|  |     derive(Desse, DesseSized) | ||||||
|  | )] | ||||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
| /// Errors that can occur during update
 | /// Errors that can occur during update
 | ||||||
| pub enum UpdateError { | pub enum UpdateError { | ||||||
|  | @ -50,7 +83,10 @@ pub enum UpdateError { | ||||||
| /// Upcoming operation of the exchange process for a given page index.
 | /// Upcoming operation of the exchange process for a given page index.
 | ||||||
| #[cfg_attr(feature = "use-defmt", derive(Format))] | #[cfg_attr(feature = "use-defmt", derive(Format))] | ||||||
| #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | ||||||
| #[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] | #[cfg_attr(
 | ||||||
|  |     any(feature = "ram-state", feature = "scratch-state"), | ||||||
|  |     derive(Desse, DesseSized) | ||||||
|  | )] | ||||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
| pub enum ExchangeStep { | pub enum ExchangeStep { | ||||||
|     /// Copy page A to the scratch page.
 |     /// Copy page A to the scratch page.
 | ||||||
|  | @ -64,7 +100,10 @@ pub enum ExchangeStep { | ||||||
| /// Store the progress of the current exchange operation
 | /// Store the progress of the current exchange operation
 | ||||||
| #[cfg_attr(feature = "use-defmt", derive(Format))] | #[cfg_attr(feature = "use-defmt", derive(Format))] | ||||||
| #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | ||||||
| #[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] | #[cfg_attr(
 | ||||||
|  |     any(feature = "ram-state", feature = "scratch-state"), | ||||||
|  |     derive(Desse, DesseSized) | ||||||
|  | )] | ||||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
| pub struct ExchangeProgress { | pub struct ExchangeProgress { | ||||||
|     /// Bank the update is coming from
 |     /// Bank the update is coming from
 | ||||||
|  | @ -82,7 +121,10 @@ pub struct ExchangeProgress { | ||||||
| /// Struct used to store the state of the bootloader situation in NVM
 | /// Struct used to store the state of the bootloader situation in NVM
 | ||||||
| #[cfg_attr(feature = "use-defmt", derive(Format))] | #[cfg_attr(feature = "use-defmt", derive(Format))] | ||||||
| #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] | ||||||
| #[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] | #[cfg_attr(
 | ||||||
|  |     any(feature = "ram-state", feature = "scratch-state"), | ||||||
|  |     derive(Desse, DesseSized) | ||||||
|  | )] | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct MoonbootState { | pub struct MoonbootState { | ||||||
|     /// If set Request, an Update is requested. This will exchange the two images, set the update
 |     /// If set Request, an Update is requested. This will exchange the two images, set the update
 | ||||||
|  | @ -112,63 +154,3 @@ const CRC: Crc<StateCrcType> = Crc::<StateCrcType>::new(&CRC_32_CKSUM); | ||||||
| fn checksum(bytes: &[u8]) -> StateCrcType { | fn checksum(bytes: &[u8]) -> StateCrcType { | ||||||
|     CRC.checksum(bytes) |     CRC.checksum(bytes) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| /// State stored in the RAM
 |  | ||||||
| /// TODO: Move to hardware folder together with state trait?
 |  | ||||||
| #[cfg(feature = "ram-state")] |  | ||||||
| pub mod ram { |  | ||||||
|     use super::*; |  | ||||||
| 
 |  | ||||||
|     /// State read and written to RAM. This assumes the device is never powered off / the ram is never
 |  | ||||||
|     /// reset!
 |  | ||||||
|     pub struct RamState; |  | ||||||
| 
 |  | ||||||
|     extern "C" { |  | ||||||
|         static mut _moonboot_state_crc_start: StateCrcType; |  | ||||||
|         static mut _moonboot_state_data_start: [u8; STATE_SERIALIZED_MAX_SIZE]; |  | ||||||
|         // TODO: Move these as normal variables to linker sections via #[link] macro?
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     impl State for RamState { |  | ||||||
|         fn read(&mut self) -> MoonbootState { |  | ||||||
|             let crc = unsafe { _moonboot_state_crc_start }; |  | ||||||
| 
 |  | ||||||
|             log::info!( |  | ||||||
|                 "Reading data with len: {}, CRC: {}", |  | ||||||
|                 STATE_SERIALIZED_MAX_SIZE, |  | ||||||
|                 crc |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             let checksum = checksum(unsafe { &_moonboot_state_data_start }); |  | ||||||
|             if crc == checksum { |  | ||||||
|                 let data = MoonbootState::deserialize_from(unsafe { &_moonboot_state_data_start }); |  | ||||||
|                 log::trace!("CRC Match! {}: {:?}", crc, data); |  | ||||||
|                 return data; |  | ||||||
|             } else { |  | ||||||
|                 log::trace!("CRC Mismatch! {} vs {}", crc, checksum); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             MoonbootState { |  | ||||||
|                 update: Update::None, |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         fn write(&mut self, data: &MoonbootState) -> Result<(), ()> { |  | ||||||
|             log::trace!("Writing data {:?}", data); |  | ||||||
| 
 |  | ||||||
|             unsafe { _moonboot_state_data_start = data.serialize() }; |  | ||||||
|             log::trace!("Written data: {:?}", unsafe { &_moonboot_state_data_start }); |  | ||||||
| 
 |  | ||||||
|             unsafe { |  | ||||||
|                 _moonboot_state_crc_start = checksum(&_moonboot_state_data_start); |  | ||||||
|             } |  | ||||||
|             log::info!( |  | ||||||
|                 "Written len: {}, checksum: {}", |  | ||||||
|                 STATE_SERIALIZED_MAX_SIZE, |  | ||||||
|                 unsafe { _moonboot_state_crc_start } |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             Ok(()) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,15 +1,63 @@ | ||||||
| use embedded_storage::Storage; | use embedded_storage::Storage; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{log, Address}; | ||||||
|     log, |  | ||||||
|     state::{ExchangeProgress, State, Update}, |  | ||||||
|     swap::{MemoryError, Swap}, |  | ||||||
|     Address, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| pub struct Ram; | use super::*; | ||||||
| 
 | 
 | ||||||
| impl Swap for Ram { | /// State read and written to RAM. This assumes the device is never powered off / the ram is never
 | ||||||
|  | /// reset!
 | ||||||
|  | pub struct RamState; | ||||||
|  | 
 | ||||||
|  | extern "C" { | ||||||
|  |     static mut _moonboot_state_crc_start: StateCrcType; | ||||||
|  |     static mut _moonboot_state_data_start: [u8; STATE_SERIALIZED_MAX_SIZE]; | ||||||
|  |     // TODO: Move these as normal variables to linker sections via #[link] macro?
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl State for RamState { | ||||||
|  |     fn read(&mut self) -> MoonbootState { | ||||||
|  |         let crc = unsafe { _moonboot_state_crc_start }; | ||||||
|  | 
 | ||||||
|  |         log::info!( | ||||||
|  |             "Reading data with len: {}, CRC: {}", | ||||||
|  |             STATE_SERIALIZED_MAX_SIZE, | ||||||
|  |             crc | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         let checksum = checksum(unsafe { &_moonboot_state_data_start }); | ||||||
|  |         if crc == checksum { | ||||||
|  |             let data = MoonbootState::deserialize_from(unsafe { &_moonboot_state_data_start }); | ||||||
|  |             log::trace!("CRC Match! {}: {:?}", crc, data); | ||||||
|  |             return data; | ||||||
|  |         } else { | ||||||
|  |             log::trace!("CRC Mismatch! {} vs {}", crc, checksum); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         MoonbootState { | ||||||
|  |             update: Update::None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn write(&mut self, data: &MoonbootState) -> Result<(), ()> { | ||||||
|  |         log::trace!("Writing data {:?}", data); | ||||||
|  | 
 | ||||||
|  |         unsafe { _moonboot_state_data_start = data.serialize() }; | ||||||
|  |         log::trace!("Written data: {:?}", unsafe { &_moonboot_state_data_start }); | ||||||
|  | 
 | ||||||
|  |         unsafe { | ||||||
|  |             _moonboot_state_crc_start = checksum(&_moonboot_state_data_start); | ||||||
|  |         } | ||||||
|  |         log::info!( | ||||||
|  |             "Written len: {}, checksum: {}", | ||||||
|  |             STATE_SERIALIZED_MAX_SIZE, | ||||||
|  |             unsafe { _moonboot_state_crc_start } | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Exchange for RamState { | ||||||
|     fn exchange<InternalMemory: Storage, HardwareState: State, const INTERNAL_PAGE_SIZE: usize>( |     fn exchange<InternalMemory: Storage, HardwareState: State, const INTERNAL_PAGE_SIZE: usize>( | ||||||
|         &mut self, |         &mut self, | ||||||
|         internal_memory: &mut InternalMemory, |         internal_memory: &mut InternalMemory, | ||||||
|  | @ -1,25 +0,0 @@ | ||||||
| pub mod ram; |  | ||||||
| pub mod scratch; |  | ||||||
| 
 |  | ||||||
| use embedded_storage::Storage; |  | ||||||
| 
 |  | ||||||
| use crate::state::{ExchangeProgress, State}; |  | ||||||
| 
 |  | ||||||
| /// Error occured during memory access
 |  | ||||||
| #[cfg_attr(feature = "use-defmt", derive(Format))] |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub enum MemoryError { |  | ||||||
|     BankSizeNotEqual, |  | ||||||
|     BankSizeZero, |  | ||||||
|     ReadFailure, |  | ||||||
|     WriteFailure, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub trait Swap { |  | ||||||
|     fn exchange<InternalMemory: Storage, HardwareState: State, const INTERNAL_PAGE_SIZE: usize>( |  | ||||||
|         &mut self, |  | ||||||
|         internal_memory: &mut InternalMemory, |  | ||||||
|         state: &mut HardwareState, |  | ||||||
|         exchange: ExchangeProgress, |  | ||||||
|     ) -> Result<(), MemoryError>; |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in a new issue