openMSX
CartridgeSlotManager.cc
Go to the documentation of this file.
2 #include "MSXMotherBoard.hh"
3 #include "HardwareConfig.hh"
4 #include "CommandException.hh"
5 #include "FileContext.hh"
6 #include "TclObject.hh"
7 #include "MSXException.hh"
8 #include "CliComm.hh"
9 #include "unreachable.hh"
10 #include "outer.hh"
11 #include "xrange.hh"
12 #include <cassert>
13 #include <memory>
14 
15 using std::string;
16 using std::vector;
17 
18 namespace openmsx {
19 
20 // CartridgeSlotManager::Slot
21 CartridgeSlotManager::Slot::~Slot()
22 {
23  assert(!config);
24  assert(useCount == 0);
25 }
26 
28 {
29  return cartCommand != nullptr;
30 }
31 
32 bool CartridgeSlotManager::Slot::used(const HardwareConfig* allowed) const
33 {
34  assert((useCount == 0) == (config == nullptr));
35  return config && (config != allowed);
36 }
37 
38 
39 // CartridgeSlotManager
41  : motherBoard(motherBoard_)
42  , cartCmd(*this, motherBoard, "cart")
43  , extSlotInfo(motherBoard.getMachineInfoCommand())
44 {
45 }
46 
48 {
49  for (auto slot : xrange(MAX_SLOTS)) {
50  (void)slot;
51  assert(!slots[slot].exists());
52  assert(!slots[slot].used());
53  }
54 }
55 
57 {
58  if (slot.size() == 1) {
59  if (('0' <= slot[0]) && (slot[0] <= '3')) {
60  return slot[0] - '0';
61  } else if (('a' <= slot[0]) && (slot[0] <= 'p')) {
62  return -(1 + slot[0] - 'a');
63  } else if (slot[0] == 'X') {
64  return -256;
65  }
66  } else if (slot.size() == 2) {
67  if ((slot[0] == '?') && ('0' <= slot[1]) && (slot[1] <= '3')) {
68  return slot[1] - '0' - 128;
69  }
70  } else if (slot == "any") {
71  return -256;
72  }
73  throw MSXException("Invalid slot specification: ", slot);
74 }
75 
77 {
78  createExternalSlot(ps, -1);
79 }
80 
82 {
83  if (isExternalSlot(ps, ss, false)) {
84  throw MSXException("Slot is already an external slot.");
85  }
86  for (auto slot : xrange(MAX_SLOTS)) {
87  if (!slots[slot].exists()) {
88  slots[slot].ps = ps;
89  slots[slot].ss = ss;
90  char slotName[] = "carta";
91  slotName[4] += slot;
92  char extName[] = "exta";
93  extName[3] += slot;
94  motherBoard.getMSXCliComm().update(
95  CliComm::HARDWARE, slotName, "add");
96  slots[slot].cartCommand = std::make_unique<CartCmd>(
97  *this, motherBoard, slotName);
98  slots[slot].extCommand = std::make_unique<ExtCmd>(
99  motherBoard, extName);
100  return;
101  }
102  }
103  UNREACHABLE;
104 }
105 
106 
107 int CartridgeSlotManager::getSlot(int ps, int ss) const
108 {
109  for (auto slot : xrange(MAX_SLOTS)) {
110  if (slots[slot].exists() &&
111  (slots[slot].ps == ps) && (slots[slot].ss == ss)) {
112  return slot;
113  }
114  }
115  UNREACHABLE; // was not an external slot
116  return 0; // avoid warning
117 }
118 
120  int ps, const HardwareConfig& allowed) const
121 {
122  testRemoveExternalSlot(ps, -1, allowed);
123 }
124 
126  int ps, int ss, const HardwareConfig& allowed) const
127 {
128  int slot = getSlot(ps, ss);
129  if (slots[slot].used(&allowed)) {
130  throw MSXException("Slot still in use.");
131  }
132 }
133 
135 {
136  removeExternalSlot(ps, -1);
137 }
138 
140 {
141  int slot = getSlot(ps, ss);
142  assert(!slots[slot].used());
143  const auto& slotName = slots[slot].cartCommand->getName();
144  motherBoard.getMSXCliComm().update(
145  CliComm::HARDWARE, slotName, "remove");
146  slots[slot].cartCommand.reset();
147  slots[slot].extCommand.reset();
148 }
149 
150 void CartridgeSlotManager::getSpecificSlot(unsigned slot, int& ps, int& ss) const
151 {
152  assert(slot < MAX_SLOTS);
153  if (!slots[slot].exists()) {
154  throw MSXException("slot-", char('a' + slot), " not defined.");
155  }
156  if (slots[slot].used()) {
157  throw MSXException("slot-", char('a' + slot), " already in use.");
158  }
159  ps = slots[slot].ps;
160  ss = slots[slot].ss;
161 }
162 
164 {
165  assert(slot < MAX_SLOTS);
166  if (!slots[slot].exists()) {
167  throw MSXException("slot-", char('a' + slot), " not defined.");
168  }
169  if (slots[slot].used()) {
170  throw MSXException("slot-", char('a' + slot), " already in use.");
171  }
172  if (slots[slot].ss != -1) {
173  throw MSXException("slot-", char('a' + slot), " is not a primary slot.");
174  }
175  assert(slots[slot].useCount == 0);
176  slots[slot].config = &hwConfig;
177  slots[slot].useCount = 1;
178  return slots[slot].ps;
179 }
180 
181 void CartridgeSlotManager::getAnyFreeSlot(int& ps, int& ss) const
182 {
183  // search for the lowest free slot
184  ps = 4; // mark no free slot
185  for (auto slot : xrange(MAX_SLOTS)) {
186  if (slots[slot].exists() && !slots[slot].used()) {
187  int p = slots[slot].ps;
188  int s = slots[slot].ss;
189  if ((p < ps) || ((p == ps) && (s < ss))) {
190  ps = p;
191  ss = s;
192  }
193  }
194  }
195  if (ps == 4) {
196  throw MSXException("Not enough free cartridge slots");
197  }
198 }
199 
201 {
202  for (auto slot : xrange(MAX_SLOTS)) {
203  if (slots[slot].exists() && (slots[slot].ss == -1) &&
204  !slots[slot].used()) {
205  assert(slots[slot].useCount == 0);
206  slots[slot].config = &hwConfig;
207  slots[slot].useCount = 1;
208  return slots[slot].ps;
209  }
210  }
211  throw MSXException("No free primary slot");
212 }
213 
215  int ps, const HardwareConfig& hwConfig)
216 {
217  int slot = getSlot(ps, -1);
218  assert(slots[slot].config == &hwConfig); (void)hwConfig;
219  assert(slots[slot].useCount == 1);
220  slots[slot].config = nullptr;
221  slots[slot].useCount = 0;
222 }
223 
225  int ps, int ss, const HardwareConfig& hwConfig)
226 {
227  for (auto slot : xrange(MAX_SLOTS)) {
228  if (!slots[slot].exists()) continue;
229  if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
230  if (slots[slot].useCount == 0) {
231  slots[slot].config = &hwConfig;
232  } else {
233  if (slots[slot].config != &hwConfig) {
234  throw MSXException(
235  "Slot ", ps, '-', ss,
236  " already in use by ",
237  slots[slot].config->getName());
238  }
239  }
240  ++slots[slot].useCount;
241  }
242  }
243  // Slot not found, was not an external slot. No problem.
244 }
245 
247  int ps, int ss, const HardwareConfig& hwConfig)
248 {
249  for (auto slot : xrange(MAX_SLOTS)) {
250  if (!slots[slot].exists()) continue;
251  if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
252  assert(slots[slot].config == &hwConfig); (void)hwConfig;
253  assert(slots[slot].useCount > 0);
254  --slots[slot].useCount;
255  if (slots[slot].useCount == 0) {
256  slots[slot].config = nullptr;
257  }
258  return;
259  }
260  }
261  // Slot not found, was not an external slot. No problem.
262 }
263 
264 bool CartridgeSlotManager::isExternalSlot(int ps, int ss, bool convert) const
265 {
266  for (auto slot : xrange(MAX_SLOTS)) {
267  int tmp = (convert && (slots[slot].ss == -1)) ? 0 : slots[slot].ss;
268  if (slots[slot].exists() &&
269  (slots[slot].ps == ps) && (tmp == ss)) {
270  return true;
271  }
272  }
273  return false;
274 }
275 
276 
277 // CartCmd
278 CartridgeSlotManager::CartCmd::CartCmd(
279  CartridgeSlotManager& manager_, MSXMotherBoard& motherBoard_,
280  string_view commandName)
281  : RecordedCommand(motherBoard_.getCommandController(),
282  motherBoard_.getStateChangeDistributor(),
283  motherBoard_.getScheduler(),
284  commandName)
285  , manager(manager_)
286  , cliComm(motherBoard_.getMSXCliComm())
287 {
288 }
289 
290 const HardwareConfig* CartridgeSlotManager::CartCmd::getExtensionConfig(
291  string_view cartname)
292 {
293  if (cartname.size() != 5) {
294  throw SyntaxError();
295  }
296  int slot = cartname[4] - 'a';
297  return manager.slots[slot].config;
298 }
299 
300 void CartridgeSlotManager::CartCmd::execute(
301  span<const TclObject> tokens, TclObject& result, EmuTime::param /*time*/)
302 {
303  string_view cartname = tokens[0].getString();
304 
305  // strip namespace qualification
306  // TODO investigate whether it's a good idea to strip namespace at a
307  // higher level for all commands. How does that interact with
308  // the event recording feature?
309  auto pos = cartname.rfind("::");
310  if (pos != string_view::npos) {
311  cartname = cartname.substr(pos + 2);
312  }
313  if (tokens.size() == 1) {
314  // query name of cartridge
315  auto* extConf = getExtensionConfig(cartname);
316  result.addListElement(strCat(cartname, ':'),
317  extConf ? extConf->getName() : string{});
318  if (!extConf) {
319  TclObject options = makeTclList("empty");
320  result.addListElement(options);
321  }
322  } else if ((tokens[1] == "eject") || (tokens[1] == "-eject")) {
323  // remove cartridge (or extension)
324  if (tokens[1] == "-eject") {
325  result =
326  "Warning: use of '-eject' is deprecated, "
327  "instead use the 'eject' subcommand";
328  }
329  if (auto* extConf = getExtensionConfig(cartname)) {
330  try {
331  manager.motherBoard.removeExtension(*extConf);
332  cliComm.update(CliComm::MEDIA, cartname, {});
333  } catch (MSXException& e) {
334  throw CommandException("Can't remove cartridge: ",
335  e.getMessage());
336  }
337  }
338  } else {
339  // insert cartridge
340  auto slotname = (cartname.size() == 5)
341  ? cartname.substr(4, 1)
342  : "any";
343  size_t extensionNameToken = 1;
344  if (tokens[1] == "insert") {
345  if (tokens.size() > 2) {
346  extensionNameToken = 2;
347  } else {
348  throw CommandException("Missing argument to insert subcommand");
349  }
350  }
351  auto options = tokens.subspan(extensionNameToken + 1);
352  try {
353  string_view romname = tokens[extensionNameToken].getString();
354  auto extension = HardwareConfig::createRomConfig(
355  manager.motherBoard, romname, slotname, options);
356  if (slotname != "any") {
357  if (auto* extConf = getExtensionConfig(cartname)) {
358  // still a cartridge inserted, (try to) remove it now
359  manager.motherBoard.removeExtension(*extConf);
360  }
361  }
362  result = manager.motherBoard.insertExtension(
363  "ROM", std::move(extension));
364  cliComm.update(CliComm::MEDIA, cartname, romname);
365  } catch (MSXException& e) {
366  throw CommandException(std::move(e).getMessage());
367  }
368  }
369 }
370 
371 string CartridgeSlotManager::CartCmd::help(const vector<string>& tokens) const
372 {
373  return strCat(
374  tokens[0], " eject : remove the ROM cartridge from this slot\n",
375  tokens[0], " insert <filename> : insert ROM cartridge with <filename>\n",
376  tokens[0], " <filename> : insert ROM cartridge with <filename>\n",
377  tokens[0], " : show which ROM cartridge is in this slot\n",
378  "The following options are supported when inserting a cartridge:\n"
379  "-ips <filename> : apply the given IPS patch to the ROM image\n"
380  "-romtype <romtype> : specify the ROM mapper type\n");
381 }
382 
383 void CartridgeSlotManager::CartCmd::tabCompletion(vector<string>& tokens) const
384 {
385  vector<const char*> extra;
386  if (tokens.size() < 3) {
387  extra = { "eject", "insert" };
388  }
389  completeFileName(tokens, userFileContext(), extra);
390 }
391 
392 bool CartridgeSlotManager::CartCmd::needRecord(span<const TclObject> tokens) const
393 {
394  return tokens.size() > 1;
395 }
396 
397 
398 // class CartridgeSlotInfo
399 
400 CartridgeSlotManager::CartridgeSlotInfo::CartridgeSlotInfo(
401  InfoCommand& machineInfoCommand)
402  : InfoTopic(machineInfoCommand, "external_slot")
403 {
404 }
405 
406 void CartridgeSlotManager::CartridgeSlotInfo::execute(
407  span<const TclObject> tokens, TclObject& result) const
408 {
409  checkNumArgs(tokens, Between{2, 3}, Prefix{2}, "?slot?");
410  auto& manager = OUTER(CartridgeSlotManager, extSlotInfo);
411  switch (tokens.size()) {
412  case 2: {
413  // return list of slots
414  string slot = "slotX";
415  for (auto i : xrange(CartridgeSlotManager::MAX_SLOTS)) {
416  if (!manager.slots[i].exists()) continue;
417  slot[4] = char('a' + i);
418  result.addListElement(slot);
419  }
420  break;
421  }
422  case 3: {
423  // return info on a particular slot
424  const auto& slotName = tokens[2].getString();
425  if ((slotName.size() != 5) || (!slotName.starts_with("slot"))) {
426  throw CommandException("Invalid slot name: ", slotName);
427  }
428  unsigned num = slotName[4] - 'a';
429  if (num >= CartridgeSlotManager::MAX_SLOTS) {
430  throw CommandException("Invalid slot name: ", slotName);
431  }
432  auto& slot = manager.slots[num];
433  if (!slot.exists()) {
434  throw CommandException("Slot '", slotName, "' doesn't currently exist in this msx machine.");
435  }
436  result.addListElement(slot.ps);
437  if (slot.ss == -1) {
438  result.addListElement("X");
439  } else {
440  result.addListElement(slot.ss);
441  }
442  if (slot.config) {
443  result.addListElement(slot.config->getName());
444  } else {
445  result.addListElement(string_view{});
446  }
447  break;
448  }
449  }
450 }
451 
452 string CartridgeSlotManager::CartridgeSlotInfo::help(
453  const vector<string>& /*tokens*/) const
454 {
455  return "Without argument: show list of available external slots.\n"
456  "With argument: show primary and secondary slot number for "
457  "given external slot.\n";
458 }
459 
460 } // namespace openmsx
void freeSlot(int ps, int ss, const HardwareConfig &hwConfig)
int allocateSpecificPrimarySlot(unsigned slot, const HardwareConfig &hwConfig)
const std::string & getMessage() const &
Definition: MSXException.hh:23
auto xrange(T e)
Definition: xrange.hh:170
CartridgeSlotManager(MSXMotherBoard &motherBoard)
Definition: span.hh:34
Commands that directly influence the MSX state should send and events so that they can be recorded by...
static std::unique_ptr< HardwareConfig > createRomConfig(MSXMotherBoard &motherBoard, string_view romfile, string_view slotname, span< const TclObject > options)
static const size_type npos
Definition: string_view.hh:24
void freePrimarySlot(int ps, const HardwareConfig &hwConfig)
bool isExternalSlot(int ps, int ss, bool convert) const
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:160
void testRemoveExternalSlot(int ps, const HardwareConfig &allowed) const
void convert(const th_ycbcr_buffer &input, RawFrame &output)
Definition: yuv2rgb.cc:342
void allocateSlot(int ps, int ss, const HardwareConfig &hwConfig)
static int getSlotNum(string_view slot)
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void getSpecificSlot(unsigned slot, int &ps, int &ss) const
CommandController & getCommandController()
bool exists(string_view filename)
Does this file (directory) exists?
size_type rfind(string_view s) const
Definition: string_view.cc:65
void getAnyFreeSlot(int &ps, int &ss) const
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
void addListElement(T t)
Definition: TclObject.hh:121
int allocateAnyPrimarySlot(const HardwareConfig &hwConfig)
string_view substr(size_type pos, size_type n=npos) const
Definition: string_view.cc:32
virtual void update(UpdateType type, string_view name, string_view value)=0
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
size_type size() const
Definition: string_view.hh:44
#define OUTER(type, member)
Definition: outer.hh:38
TclObject makeTclList(Args &&... args)
Definition: TclObject.hh:280
StateChangeDistributor & getStateChangeDistributor()
#define UNREACHABLE
Definition: unreachable.hh:38